commit 2b0debb847ac6cdd689bd6b81ca9779fb3a589b9 Author: Jason Date: Thu Mar 17 15:59:24 2022 +0800 first commit diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..cd710f4 --- /dev/null +++ b/Makefile @@ -0,0 +1,21 @@ +build_amd: + @echo '┌ start build amd64' + @bash ./script/build/multi-service.sh + @echo '└ end build amd64' + +build_arm: + @echo '┌ start build arm64' + @bash ./script/build/multi-service.sh arm64 + @echo '└ end build arm64' + +# 编译二进制 amd64 +quick_build_amd: + @echo '┌ start quick build amd64' + @bash ./script/build/quick_build.sh + @echo '└ end quick build amd64' + +# 编译二进制 amr64 +quick_build_arm: + @echo '┌ start quick build arm64' + @bash ./script/build/quick_build.sh arm64 + @echo '└ end quick build arm64' \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c538747 --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +## 配置文件 + +```toml +# Env 三种模式 +Env = "debug" # 测试环境 +Env = "release" # 线上环境 +Env = "benchmark" # 压测环境 +``` + +## 部署环境 + +环境需要增加以下环境变量 +GOLANG_PROTOBUF_REGISTRATION_CONFLICT=warn + +## 快速部署 + +`make build_xxx` xxx 可选为 amd 或者 arm +如果要选择平台则自行修改`dtalk/script/build/util.sh`中的`initOS()`编译设置 + +`make build` 包括编译二进制, 配置文件并打包 + +`make quick_build` 仅编译二进制 \ No newline at end of file diff --git a/doc/auth.md b/doc/auth.md new file mode 100644 index 0000000..8048833 --- /dev/null +++ b/doc/auth.md @@ -0,0 +1,57 @@ +# 鉴权服务 + +#### 应用注册 + +URL: /auth/sign-in + +`post` + +**请求参数:** + +| **参数** | **名字** | **类型** | **约束** | **说明** | +| -------------- | ------------ | -------- | -------- | -------- | +| Content-Type | body中的数据类型 | string | true | 值为multipart/form-data | +| configFile | 配置文件 | file | true | 包含第三方应用鉴权服务接口的相关信息 | +| appId | 应用ID | text | true | 应用ID,位于请求头 | + + +**返回参数:** + +| **参数** | **名字** | **类型** | **说明** | +| ----------- | ------------ | -------- | ---------- | +| appId | appId | string | appId | +| key | key | string | 授权第三方应用使用本鉴权服务 | +| createTime | 创建时间 | int | app注册时间 | +| updateTime | 更新时间 | int | 信息更新时间 | + +```json +{ + "result": 0, + "message": "", + "data": { + "appId": "dtalk", + "key": "key", + "createTime": 1625552582748, + "updateTime": 1625552582748 + } +} +``` + + + +#### 鉴权 + +**请求参数:** + +| **参数** | **名字** | **类型** | **约束** | **说明** | +| ---------- | -------------- | -------- | -------- | ------------------------------------------------------------ | +| appId | 应用的唯一标识 | string | true | 应用的唯一标识 | +| token | token | string | true | 第三方鉴权所要的token | +| digest | 消息摘要 | string | true | digest = SHA256 ( appId + token + createTime + key )(key为注册时所返回的key) | +| createTime | 请求创建时间 | int | true | 请求创建时的时间戳 | + +**返回参数:** + +| **参数** | **名字** | **类型** | **说明** | +| -------- | -------- | -------- | -------- | +| uid | 用户ID | string | 用户ID | diff --git a/doc/backend.md b/doc/backend.md new file mode 100644 index 0000000..66c3204 --- /dev/null +++ b/doc/backend.md @@ -0,0 +1,406 @@ +# 后台服务 + +#### 检查更新 +URL: /app/version/check + +`post` + +**请求参数:** + +| **参数** | **名字** | **类型** | **约束** | **说明** | +| -------------- | ------------ | -------- | -------- | -------- | +| versionCode | 当前版本 | int | true | 版本code | + +**返回参数:** + +| **参数** | **名字** | **类型** | **说明** | +| ----------- | ------------ | -------- | ---------- | +| id | 最新的版本 | int | 版本编号 | +| platform | 平台 | string | chat33pro | +| status | 线上状态 | int | 0:历史;1:线上版本 | +| deviceType | 终端类型 | string | Android/IOS | +| versionName | 版本名 | string | 3.6.8.10 | +| versionCode | 版本code | int | 36810 | +| url | 下载地址 | string | | +| force | 是否强制更新 | bool | false:非强制;true:强制 | +| description | 描述信息 | string array | | +| opeUser | 操作者 | string | | +| updateTime | 更新时间 | int | | +| createTime | 创建时间 | int | | +| size | 包大小 | int | 单位:byte | +| md5 | 包的md5 | string | | + + +```json +{ + "result": 0, + "message": "", + "data": { + "id": 8, + "platform": "Chat33Pro", + "status": 1, + "deviceType": "IOS", + "versionName": "1.0.4", + "versionCode": 10400, + "url": "https://xxx", + "force": true, + "description": [ + "qqq", + "ww" + ], + "opeUser": "root", + "md5": "12345", + "size": 123, + "updateTime": 1621408163504, + "createTime": 1621396088880 + } +} +``` + +#### 创建版本 + +URL: /backend/version/create + +`post` + +**请求参数:** + +| 参数 | 名字 | 类型 | 约束 | 说明 | +| ----------- | ------------ | ------------ | ---- | ------------------------- | +| platform | 平台 | string | true | chat33pro | +| description | 描述信息 | string array | true | | +| force | 是否强制更新 | bool | true | false:非强制;true:强制 | +| url | 下载地址 | string | true | | +| versionCode | 版本code | int | true | 36810 | +| versionName | 版本名 | string | true | 3.6.8.10 | +| deviceType | 终端类型 | string | true | Android/IOS | +| Authorization | 授权 | string | true | 用于传递token | +| size | 包大小 | int | 单位:byte | +| md5 | 包的md5 | string | | + +**返回参数:** + +| 参数 | 名字 | 类型 | 说明 | +| ----------- | ------------ | ------------ | ------------------------- | +| version | 版本信息 | object | 创建的版本的全部信息 | +| id | 版本编号 | int | | +| platform | 平台 | string | chat33pro | +| status | 线上状态 | int | 0:历史;1:线上版本 | +| deviceType | 终端类型 | string | Android/IOS | +| versionName | 版本名 | string | 3.6.8.10 | +| versionCode | 版本code | int | 36810 | +| url | 下载地址 | string | | +| force | 是否强制更新 | bool | false:非强制;true:强制 | +| description | 描述信息 | string array | | +| opeUser | 操作者 | string | | +| updateTime | 更新时间 | int | | +| createTime | 创建时间 | int | | +| size | 包大小 | int | 单位:byte | +| md5 | 包的md5 | string | | + +```json +{ + "result": 0, + "message": "", + "data": { + "version": { + "id": 3, + "platform": "Chat33Pro", + "status": 0, + "deviceType": "Android", + "versionName": "1.0.1", + "versionCode": 10000, + "url": "https://xxx", + "force": true, + "description": [ + "qqq", + "ww" + ], + "opeUser": "root", + "md5": "12345", + "size": 123, + "updateTime": 1621394358387, + "createTime": 1621394358387 + } + } +} +``` + + + +#### 更新版本 + +URL: /backend/version/update + +`put` + +**请求参数:** + +| 参数 | 名字 | 类型 | 约束 | 说明 | +| ----------- | ------------ | ------------ | ---- | ------------------------- | +| description | 描述信息 | string array | true | | +| force | 是否强制更新 | bool | true | false:非强制;true:强制 | +| url | 下载地址 | string | true | | +| versionCode | 版本code | int | true | 36810 | +| versionName | 版本名 | string | true | 3.6.8.10 | +| id | 版本编号 | int | true | | +| Authorization | 授权 | string | true | 用于传递token | +| size | 包大小 | int | 单位:byte | +| md5 | 包的md5 | string | | + +**返回参数:** + +| 参数 | 名字 | 类型 | 说明 | +| ----------- | ------------ | ------------ | ------------------------- | +| version | 版本信息 | object | 修改后的版本的全部信息 | +| id | 版本编号 | int | | +| platform | 平台 | string | chat33pro | +| status | 线上状态 | int | 0:历史;1:线上版本 | +| deviceType | 终端类型 | string | Android/IOS | +| versionName | 版本名 | string | 3.6.8.10 | +| versionCode | 版本code | int | 36810 | +| url | 下载地址 | string | | +| force | 是否强制更新 | bool | false:非强制;true:强制 | +| description | 描述信息 | string array | | +| opeUser | 操作者 | string | | +| updateTime | 更新时间 | int | | +| createTime | 创建时间 | int | | +| size | 包大小 | int | 单位:byte | +| md5 | 包的md5 | string | | + +```json +{ + "result": 0, + "message": "", + "data": { + "version": { + "id": 3, + "platform": "Chat33Pro", + "status": 0, + "deviceType": "Android", + "versionName": "1.0.0", + "versionCode": 10000, + "url": "https://xxx", + "force": false, + "description": [ + "xx", + "yy" + ], + "opeUser": "root", + "md5": "234567", + "size": 2345, + "updateTime": 1621395843469, + "createTime": 1621394358387 + } + } +} +``` + +#### 修改版本线上状态 + +URL: /backend/version/change-status + +`put` + +**请求参数:** + +| 参数 | 名字 | 类型 | 约束 | 说明 | +| ------ | -------- | ------ | ---- | -------------------- | +| id | 版本编号 | int | true | 要修改的版本编号 | +| Authorization | 授权 | string | true | 用于传递token | + +**返回参数:** + +无 + +```json +{ + "result": 0, + "message": "", + "data": null +} +``` + +#### 获取全部版本信息 + +URL: /backend/version/list + +`get` + +**请求参数:** + +| 参数 | 名字 | 类型 | 约束 | 说明 | +| ---------- | -------- | ------ | ----- | ------------ | +| page | 页码 | int | false | 从0开始,不填默认是0 | +| platform | 平台 | string | false | 要筛选的平台 | +| deviceType | 终端类型 | string | false | 要筛选的终端 | +| Authorization | 授权 | string | true | 用于传递token | + +**返回参数:** + +| 参数 | 名字 | 类型 | 说明 | +| ------------- | -------------- | ------------ | -------------------------- | +| totalElements | 所有的记录条数 | int | | +| totalPages | 总页数 | int | | +| versionList | 版本列表 | object array | 所查询的全部版本的全部信息 | +| id | 版本编号 | int | | +| platform | 平台 | string | chat33pro | +| status | 线上状态 | int | 0:历史;1:线上版本 | +| deviceType | 终端类型 | string | Android/IOS | +| versionName | 版本名 | string | 3.6.8.10 | +| versionCode | 版本code | int | 36810 | +| url | 下载地址 | string | | +| force | 是否强制更新 | bool | false:非强制;true:强制 | +| description | 描述信息 | string array | | +| opeUser | 操作者 | string | | +| updateTime | 更新时间 | int | | +| createTime | 创建时间 | int | | +| size | 包大小 | int | 单位:byte | +| md5 | 包的md5 | string | | + +```json +{ + "result": 0, + "message": "", + "data": { + "totalElements": 5, + "totalPages": 1, + "versionList": [ + { + "id": 12, + "platform": "Chat33Pro", + "status": 0, + "deviceType": "IOS", + "versionName": "1.0.0", + "versionCode": 10000, + "url": "https://xxx", + "force": false, + "description": [ + "qqq", + "ww" + ], + "opeUser": "root", + "md5": "12345", + "size": 123, + "updateTime": 1621396134321, + "createTime": 1621396134321 + }, + { + "id": 11, + "platform": "Chat33Pro", + "status": 1, + "deviceType": "IOS", + "versionName": "1.0.1", + "versionCode": 10100, + "url": "https://xxx", + "force": true, + "description": [ + "qqq", + "ww" + ], + "opeUser": "root", + "md5": "12345", + "size": 123, + "updateTime": 1621396320416, + "createTime": 1621396123534 + }, + { + "id": 10, + "platform": "Chat33Pro", + "status": 0, + "deviceType": "IOS", + "versionName": "1.0.2", + "versionCode": 10200, + "url": "https://xxx", + "force": true, + "description": [ + "qqq", + "ww" + ], + "opeUser": "root", + "md5": "12345", + "size": 123, + "updateTime": 1621396117028, + "createTime": 1621396117028 + }, + { + "id": 9, + "platform": "Chat33Pro", + "status": 0, + "deviceType": "IOS", + "versionName": "1.0.3", + "versionCode": 10300, + "url": "https://xxx", + "force": false, + "description": [ + "qqq", + "ww" + ], + "opeUser": "root", + "md5": "12345", + "size": 123, + "updateTime": 1621396108579, + "createTime": 1621396108579 + }, + { + "id": 8, + "platform": "Chat33Pro", + "status": 0, + "deviceType": "IOS", + "versionName": "1.0.4", + "versionCode": 10400, + "url": "https://xxx", + "force": true, + "description": [ + "qqq", + "ww" + ], + "opeUser": "root", + "md5": "12345", + "size": 123, + "updateTime": 1621396320416, + "createTime": 1621396088880 + } + ] + } +} +``` + +#### 获取token + +URL: /backend/user/login + +`get` + +**请求参数:** + +| 参数 | 名字 | 类型 | 约束 | 说明 | +| -------- | ------ | ------ | ---- | ------------ | +| userName | 用户名 | string | true | 暂时为“root” | +| password | 口令 | string | true | 暂时为“root” | + +**返回参数:** + +| 参数 | 名字 | 类型 | 说明 | +| ----- | ----- | ------ | ---- | +| userInfo | 用户信息 | object |包含用户名和token | +| userName | 用户名 | string | | +| token | token | string | | + +```json +{ + "result": 0, + "message": "", + "data": { + "userInfo": { + "userName": "root", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InJvb3QiLCJleHAiOjE2MjA5ODcyMDEsImlzcyI6IkJvYiJ9.w_NoSezjjJLRJMjiU4jiMYozdYvL6NPwv2xuCMepws4" + } + } +} +``` + + + + + diff --git a/doc/backupApi.md b/doc/backupApi.md new file mode 100644 index 0000000..72c8823 --- /dev/null +++ b/doc/backupApi.md @@ -0,0 +1,516 @@ +# 找回服务 +### 手机绑定查询 +URL: /backup/phone-query + +`get`|`post` + +**请求参数:** + +| 参数名 | 必选 | 类型 | 说明 | +| ------ | ---- | ------ | ------ | +| area | true | string | 区号 | +| phone | true | string | 手机号 | + +**返回参数:** + +```json +{ + "result": 0, + "message": "操作成功", + "data": { + "exists":false + } +} +``` + +### 邮箱绑定查询 +URL: /backup/email-query + +`get`|`post` + +**请求参数:** + +| 参数名 | 必选 | 类型 | 说明 | +| ------ | ---- | ------ | ---- | +| email | true | string | 邮箱 | + +**返回参数:** + +```json +{ + "result": 0, + "message": "操作成功", + "data": { + "exists":false + } +} +``` + +### 发送手机验证码 +URL: /backup/v2/phone-send + +`post` + +**请求参数:** + +| 参数名 | 必选 | 类型 | 说明 | +| ------ | ----- | ------ | ------ | +| area | false | string | 区号 | +| phone | true | string | 手机号 | +| codeType | true | string | 验证码类型:quick->快速登录;bind->绑定;export->导出通讯录 | + +**返回参数:** + +```json +{ + "result": 0, + "message": "操作成功", + "data": { + } +} +``` + +### 发送邮箱验证码 + +URL: /backup/v2/email-send + +`post` + +**请求参数:** + +| 参数名 | 必选 | 类型 | 说明 | +| ------ | ---- | ------ | ---- | +| email | true | string | 邮箱 | +| codeType | true | string | 验证码类型:quick->快速登录;bind->绑定;export->导出通讯录 | + +**返回参数:** + +```json +{ + "result": 0, + "message": "操作成功", + "data": { + } +} +``` + +### 绑定手机 + +URL: /backup/v2/phone-binding + +`post`|`auth` + +type: bind + +**请求参数:** + +| 参数名 | 必选 | 类型 | 说明 | +| -------- | ----- | ------ | ---------- | +| area | false | string | 区号 | +| phone | true | string | 手机号 | +| code | true | string | 验证码 | +| mnemonic | true | string | 加密助记词 | + +**返回参数:** + +```json +{ + "result": 0, + "message": "操作成功", + "data": { + } +} +``` + +### 绑定邮箱 + +URL: /backup/v2/email-binding + +`post`|`auth` + +type: bind + +**请求参数:** + +| 参数名 | 必选 | 类型 | 说明 | +| -------- | ---- | ------ | ---------- | +| email | true | string | 手机号 | +| code | true | string | 验证码 | +| mnemonic | true | string | 加密助记词 | + +**返回参数:** + +```json +{ + "result": 0, + "message": "操作成功", + "data": { + } +} +``` + +### 手机找回私钥 + +URL: /backup/v2/phone-retrieve + +`post` + +type: quick + +**请求参数:** + +| 参数名 | 必选 | 类型 | 说明 | +| ------ | ---- | ------ | ------ | +| area | false | string | 区号 | +| phone | true | string | 手机号 | +| code | true | string | 验证码 | + +**返回参数:** + +| 参数名 | 必选 | 类型 | 说明 | +| ----------- | ---- | ------ | ---------- | +| address | true | string | 用户地址 | +| area | true | string | 区号 | +| phone | true | string | 手机号 | +| email | true | string | 邮箱 | +| mnemonic | true | string | 加密助记词 | +| update_time | true | string | 更新时间 | + +```json +{ + "result": 0, + "message": "操作成功", + "data": { + "address": "0xffffffff", + "area":"", + "phone":"", + "email":"", + "mnemonic":"", + "update_time":"" + } +} +``` +### 邮箱找回私钥 + +URL: /backup/v2/email-retrieve + +`post` + +type: quick + +**请求参数:** + +| 参数名 | 必选 | 类型 | 说明 | +| ------ | ---- | ------ | ------ | +| email | true | string | 邮箱 | +| code | true | string | 验证码 | + +**返回参数:** + +| 参数名 | 必选 | 类型 | 说明 | +| ----------- | ---- | ------ | ---------- | +| address | true | string | 用户地址 | +| area | true | string | 区号 | +| phone | true | string | 手机号 | +| email | true | string | 邮箱 | +| mnemonic | true | string | 加密助记词 | +| update_time | true | string | 更新时间 | + +```json +{ + "result": 0, + "message": "操作成功", + "data": { + "address": "0xffffffff", + "area":"", + "phone":"", + "email":"", + "mnemonic":"", + "update_time":"" + } +} +``` + +### 手机导出通讯录验证 + +URL: /backup/v2/phone-export + +`post` + +type: export + +**请求参数:** + +| 参数名 | 必选 | 类型 | 说明 | +| ------ | ---- | ------ | ------ | +| area | false | string | 区号 | +| phone | true | string | 手机号 | +| code | true | string | 验证码 | +| address | true | string | 地址 | + +**返回参数:** + +ERR:-4010 账号与绑定手机不一致 +```json +{ + "result": 0, + "message": "操作成功", + "data": { + } +} +``` + +### 邮箱导出通讯录验证 + +URL: /backup/v2/email-export + +`post` + +type: export + +**请求参数:** + +| 参数名 | 必选 | 类型 | 说明 | +| ------ | ---- | ------ | ------ | +| email | true | string | 邮箱 | +| code | true | string | 验证码 | +| address | true | string | 地址 | + +**返回参数:** + +ERR:-4011 账号与绑定邮箱不一致 + +```json +{ + "result": 0, + "message": "操作成功", + "data": { + } +} +``` + +### 修改助记词(修改密聊密码) + +URL: /backup/edit-mnemonic + +`post`|`auth` + +**请求参数:** + +| 参数名 | 必选 | 类型 | 说明 | +| -------- | ---- | ------ | ---------- | +| mnemonic | true | string | 加密助记词 | + +**返回参数:** + +```json +{ + "result": 0, + "message": "操作成功", + "data": { + } +} +``` + + +### 发送手机验证码 +URL: /backup/phone-send(弃用) + +`post` + +**请求参数:** + +| 参数名 | 必选 | 类型 | 说明 | +| ------ | ----- | ------ | ------ | +| area | false | string | 区号 | +| phone | true | string | 手机号 | + +**返回参数:** + +```json +{ + "result": 0, + "message": "操作成功", + "data": { + } +} +``` + +### 发送邮箱验证码 + +URL: /backup/email-send(弃用) + +`post` + +**请求参数:** + +| 参数名 | 必选 | 类型 | 说明 | +| ------ | ---- | ------ | ---- | +| email | true | string | 邮箱 | + +**返回参数:** + +```json +{ + "result": 0, + "message": "操作成功", + "data": { + } +} +``` + +### 绑定手机 + +URL: /backup/phone-binding(弃用) + +`post`|`auth` + +**请求参数:** + +| 参数名 | 必选 | 类型 | 说明 | +| -------- | ----- | ------ | ---------- | +| area | false | string | 区号 | +| phone | true | string | 手机号 | +| code | true | string | 验证码 | +| mnemonic | true | string | 加密助记词 | + +**返回参数:** + +```json +{ + "result": 0, + "message": "操作成功", + "data": { + } +} +``` + +### 绑定邮箱 + +URL: /backup/email-binding(弃用) + +`post`|`auth` + +**请求参数:** + +| 参数名 | 必选 | 类型 | 说明 | +| -------- | ---- | ------ | ---------- | +| email | true | string | 手机号 | +| code | true | string | 验证码 | +| mnemonic | true | string | 加密助记词 | + +**返回参数:** + +```json +{ + "result": 0, + "message": "操作成功", + "data": { + } +} +``` + +### 手机找回私钥 + +URL: /backup/phone-retrieve(弃用) + +`post` + +**请求参数:** + +| 参数名 | 必选 | 类型 | 说明 | +| ------ | ---- | ------ | ------ | +| area | true | string | 区号 | +| phone | true | string | 手机号 | +| code | true | string | 验证码 | + +**返回参数:** + +| 参数名 | 必选 | 类型 | 说明 | +| ----------- | ---- | ------ | ---------- | +| address | true | string | 用户地址 | +| area | true | string | 区号 | +| phone | true | string | 手机号 | +| email | true | string | 邮箱 | +| mnemonic | true | string | 加密助记词 | +| update_time | true | string | 更新时间 | + +```json +{ + "result": 0, + "message": "操作成功", + "data": { + "address": "0xffffffff", + "area":"", + "phone":"", + "email":"", + "mnemonic":"", + "update_time":"" + } +} +``` +### 邮箱找回私钥 + +URL: /backup/email-retrieve(弃用) + +`post` + +**请求参数:** + +| 参数名 | 必选 | 类型 | 说明 | +| ------ | ---- | ------ | ------ | +| email | true | string | 邮箱 | +| code | true | string | 验证码 | + +**返回参数:** + +| 参数名 | 必选 | 类型 | 说明 | +| ----------- | ---- | ------ | ---------- | +| address | true | string | 用户地址 | +| area | true | string | 区号 | +| phone | true | string | 手机号 | +| email | true | string | 邮箱 | +| mnemonic | true | string | 加密助记词 | +| update_time | true | string | 更新时间 | + +```json +{ + "result": 0, + "message": "操作成功", + "data": { + "address": "0xffffffff", + "area":"", + "phone":"", + "email":"", + "mnemonic":"", + "update_time":"" + } +} +``` + +### bty和btc地址映射(第一阶段) + +URL: /backup/transform/addressEnrolment + +`post` + +**请求参数:** + +| 参数名 | 必选 | 类型 | 说明 | +| ------ | ---- | ------ | ------ | +| btcAddress | true | string | | +| ethAddress | true | string | | + +**返回参数:** + +| 参数名 | 必选 | 类型 | 说明 | +| ----------- | ---- | ------ | ---------- | + +```json +{ + "result": 0, + "message": "操作成功", + "data": { + } +} +``` \ No newline at end of file diff --git a/doc/discoveryApi.md b/doc/discoveryApi.md new file mode 100644 index 0000000..de1d90b --- /dev/null +++ b/doc/discoveryApi.md @@ -0,0 +1,42 @@ +# 发现服务 +#### 获取聊天服务列表 +URL: /disc/nodes + +`post` + +**请求参数:**无 + +**返回参数:** + +| 参数名 | 必填 | 类型 | 说明 | +| ------- | ---- | -------- | -------------- | +| servers | true | []object | 聊天服务器列表 | +| name | true | string | 名称 | +| address | true | string | 地址 | +| nodes | true | []object | 合约节点列表 | +| name | true | string | 名称 | +| address | true | string | 地址 | + +```json +{ + "result": 0, + "message": "操作成功", + "data": { + "servers": [ + { + "name": "默认服务器", + "address":"127.0.0.1:8080" + },{ + "name": "默认服务器2", + "address":"chat.33.cn" + } + ], + "nodes": [ + { + "name": "合约节点1", + "address":"127.0.0.1:8080" + } + ] + } +} +``` \ No newline at end of file diff --git a/doc/gateway.md b/doc/gateway.md new file mode 100644 index 0000000..781813b --- /dev/null +++ b/doc/gateway.md @@ -0,0 +1,95 @@ +#### 获取模块启用状态 +URL: /app/modules/all + +`post` + +**请求参数:** + +无 + +**返回参数:** + +| **参数** | **名字** | **类型** | **说明** | +| ----------- | ------------ | -------- | ---------- | +| name | 模块名称 | string | 钱包模块:wallet,企业模块:oa,红包模块:redpacket | +| isEnabled | 是否启用 | bool | | +| endPoints | 模块访问地址 | string | | + +```json +{ + "result": 0, + "message": "", + "data": [ + { + "name": "wallet", + "isEnabled": true, + "endPoints": [ + "https://172.16.101.126:8083" + ] + }, + { + "name": "oa", + "isEnabled": true, + "endPoints": [ + "http://127.0.0.1" + ] + } + ] +} +``` + +#### 消息撤回 +URL: /app/record/revoke + +`post` +**请求参数:** + +| **参数** | **名字** | **类型** | **约束** | **说明** | +| ----------- | ------------ | -------- | ---------- |---------- | +| type | 类型 | int | 必填 | 0->撤回私聊消息;1->撤回群聊消息 | +| logId | 消息id | int64 | 必填| | + +```json +{ + "type": 0, + "logId": 0 +} +``` + +**返回参数:** +无 +```json +{ + "result": 0, + "message": "", + "data": {} +} +``` + +#### 关注消息 +URL: /app/record/focus + +`post` `auth` +**请求参数:** + +| **参数** | **名字** | **类型** | **约束** | **说明** | +| ----------- | ------------ | -------- | ---------- |---------- | +| type | 类型 | int | 必填 | 0->关注私聊消息;1->关注群聊消息 | +| logId | 消息id | int64 | 必填| | + +```json +{ + "type": 0, + "logId": 0 +} +``` + +**返回参数:** +无 +```json +{ + "result": 0, + "message": "", + "data": {} +} +``` diff --git a/doc/group.md b/doc/group.md new file mode 100644 index 0000000..77a5bfb --- /dev/null +++ b/doc/group.md @@ -0,0 +1,758 @@ +# 以最新swagger为准 +[http://172.16.101.107:8888/group/swagger/index.html](http://172.16.101.107:8888/group/swagger/index.html) + +## 群服务 + + +测试地址 172.16.101.107:8888/group/app + + + +## 已完成接口 + +| 路由 | 说明 | +| ------------------------------------- | ---------- | +| `POST` URL: /app/create-group | 创建群 | +| `POST` URL:/app /invite-group-members | 邀请新群友 | +| `POST` URL: /app/group-info | 群信息 | +| `POST` URL: /app/group-list | 群列表 | +| `POST` URL: /app/group-member-list | 群成员列表 | +| `POST`URL:/app/group-member-info | 群成员信息 | +| `POST` URL:/app/group-remove | 踢群成员 | +| `POST` URL:/app/group-exit | 退出群 | +| `POST` URL:/app/group-disband | 解散群 | + + + + +### 创建群+ + +`POST` URL: /app/create-group + + + +**Herder** + +`FZM-SIGNATURE` = token + + + +**请求参数:** + +| 参数名 | 必选 | 类型 | 说明 | +| --------- | ----- | -------- | ---------- | +| name | false | string | 群名称 | +| avatar | false | string | 群头像 url | +| introduce | false | string | 群简介 | +| memberIds | false | []string | 新群员 id | + + + +**请求参数示例** + +```json +{ + "name": "test-group-1", + "avatar": "", + "memberIds": [ + "member-1", "member-2" + ] +} +``` + + + +**返回参数(data):** + +| 参数名 | 类型 | 说明 | +| ---------- | ------------ | ------------------------------------------------------------ | +| id | int | 群id | +| markId | string | 群短 id(暂时没用, 后面可以供搜索加群使用) | +| name | string | 群名称 | +| avatar | string | 群头像 url | +| introduce | string | 群简介 | +| owner | MemberInfo | 群主信息 | +| members | []MemberInfo | 群成员信息list | +| memberNum | int | 群总人数 | +| maximum | int | 群成员人数上限 | +| status | int | 群状态,0=正常 1=封禁 2=解散 | +| createTime | int | 群创建时间 | +| joinType | int | 加群方式,0=允许任何方式加群,1=群成员邀请加群,2=群主和管理员邀请加群 | +| muteType | int | 禁言, 0=所有人可以发言, 1=群主和管理员可以发言 | + + + +**MemberInfo 参数类型** + +| 参数名 | 类型 | 说明 | +| ---------- | ------ | ------------------------------------------ | +| memberId | string | 用户id | +| memberName | string | 用户群昵称 | +| memberType | int | 用户角色,2=群主,1=管理员,0=群员,3=退群 | + + + +**返回参数示例:** + +```json +{ + "result": 0, + "message": "", + "data": { + "id": 127043701116506112, + "markId": "00351854", + "name": "test-group-1", + "avatar": "", + "introduce": "", + "owner": { + "memberId": "1FKxgaEh5fuSm7a35BfUnKYAmradowpiTR", + "memberName": "", + "memberType": 2 + }, + "members": [ + { + "memberId": "member-1", + "memberName": "", + "memberType": 0 + }, + { + "memberId": "member-2", + "memberName": "", + "memberType": 0 + } + ], + "memberNum": 3, + "maximum": 200, + "status": 0, + "createTime": 1621230378707, + "joinType": 0, + "muteType": 0 + } +} +``` + + + +### 邀请新群友+ + +`POST` URL: /app/invite-group-members + + + +**Herder** + +`FZM-SIGNATURE` = token + + + +**请求参数:** + +| 参数名 | 必选 | 类型 | 说明 | +| ------------ | ---- | -------- | ---------- | +| id | true | int64 | 群ID | +| newMemberIds | true | []string | 被邀请人ID | + + + +**请求参数示例** + +```json +{ + "id": 127043701116506112, + "newMemberIds": [ + "member-5", + "member-6" + ] +} +``` + + + +**返回参数(data):** + +| 参数名 | 类型 | 说明 | +| ---------- | ------------ | ----------------- | +| id | int | 群id | +| memberNum | int | 群总人数 | +| inviter | MemberInfo | 邀请人信息 | +| newMembers | []MemberInfo | 被邀请人信息 list | + + + +**MemberInfo 参数类型** + +| 参数名 | 类型 | 说明 | +| ---------- | ------ | ------------------------------------------ | +| memberId | string | 用户id | +| memberName | string | 用户群昵称 | +| memberType | int | 用户角色,2=群主,1=管理员,0=群员,3=退群 | + + + +**返回参数示例:** + +```json +{ + "result": 0, + "message": "", + "data": { + "id": 125290793882619904, + "memberNum": 5, + "inviter": { + "memberId": "chenhongyu", + "memberName": "", + "memberType": 0 + }, + "newMembers": [ + { + "memberId": "member-4", + "memberName": "", + "memberType": 2 + } + ] + } +} +``` + + + +### 群信息+ + +`POST` URL: /app/group-info + + + +**Herder** + +`FZM-SIGNATURE` = token + + + +**请求参数:** + +| 参数名 | 必选 | 类型 | 说明 | +| ------ | ---- | ---- | ---- | +| id | true | int | 群ID | + + + +**请求参数示例** + +```json +{ + "id":127082931377147904 +} +``` + + + +**返回参数(data):** + +| 参数名 | 类型 | 说明 | +| ---------- | ------------ | ------------------------------------------------------------ | +| id | int | 群id | +| markId | string | 群短 id(暂时没用, 后面可以供搜索加群使用) | +| name | string | 群名称 | +| avatar | string | 群头像 url | +| introduce | string | 群简介 | +| owner | MemberInfo | 群主信息 | +| person | MemberInfo | 本人信息 | +| members | []MemberInfo | 所有群成员信息list | +| memberNum | int | 群总人数 | +| maximum | int | 群成员人数上限 | +| status | int | 群状态,0=正常 1=封禁 2=解散 | +| createTime | int | 群创建时间 | +| joinType | int | 加群方式,0=允许任何方式加群,1=群成员邀请加群,2=群主和管理员邀请加群 | +| muteType | int | 禁言, 0=所有人可以发言, 1=群主和管理员可以发言 | + + + +**MemberInfo 参数类型** + +| 参数名 | 类型 | 说明 | +| ---------- | ------ | ------------------------------------------ | +| memberId | string | 用户id | +| memberName | string | 用户群昵称 | +| memberType | int | 用户角色,0=群主,1=管理员,2=群员,3=退群 | + + + +**返回参数示例:** + +```json +{ + "result": 0, + "message": "", + "data": { + "id": 127043701116506112, + "markId": "", + "name": "", + "avatar": "", + "introduce": "", + "owner": { + "memberId": "1FKxgaEh5fuSm7a35BfUnKYAmradowpiTR", + "memberName": "", + "memberType": 2 + }, + "members": [ + { + "memberId": "1FKxgaEh5fuSm7a35BfUnKYAmradowpiTR", + "memberName": "", + "memberType": 2 + }, + { + "memberId": "member-1", + "memberName": "", + "memberType": 0 + }, + { + "memberId": "member-2", + "memberName": "", + "memberType": 0 + }, + { + "memberId": "member-5", + "memberName": "", + "memberType": 0 + }, + { + "memberId": "member-6", + "memberName": "", + "memberType": 0 + } + ], + "memberNum": 5, + "maximum": 200, + "status": 0, + "createTime": 1621230378707, + "joinType": 0, + "muteType": 0 + } +} +``` + + + + + +### 群列表+ + +`GET` | `POST` URL: /app/group-list + + + +**Herder** + +`FZM-SIGNATURE` = token + + + +**请求参数:** + +| 参数名 | 必选 | 类型 | 说明 | +| ------ | ---- | ---- | ---- | +| | | | | + + + +**请求参数示例** + +```json + +``` + + + +**返回参数(data):** + +| 参数名 | 类型 | 说明 | +| ------ | ----------- | ----------- | +| groups | []GroupInfo | 群信息 list | + + + +**GroupInfo:** + +| 参数名 | 类型 | 说明 | +| ---------- | ---------- | ------------------------------------------------------------ | +| id | int | 群id | +| markId | string | 群短 id(暂时没用, 后面可以供搜索加群使用) | +| name | string | 群名称 | +| avatar | string | 群头像 url | +| introduce | string | 群简介 | +| owner | MemberInfo | 群主信息 | +| memberNum | int | 群总人数 | +| maximum | int | 群成员人数上限 | +| status | int | 群状态,0=正常 1=封禁 2=解散 | +| createTime | int | 群创建时间 | +| joinType | int | 加群方式,0=允许任何方式加群,1=群成员邀请加群,2=群主和管理员邀请加群 | +| muteType | int | 禁言, 0=所有人可以发言, 1=群主和管理员可以发言 | + + + + +**返回参数示例:** + +```json +{ + "result": 0, + "message": "", + "data": { + "groups": [ + { + "id": 127043701116506112, + "markId": "00351854", + "name": "test-group-1", + "avatar": "", + "introduce": "", + "owner": { + "memberId": "1FKxgaEh5fuSm7a35BfUnKYAmradowpiTR", + "memberName": "", + "memberType": 2 + }, + "memberNum": 5, + "maximum": 200, + "status": 0, + "createTime": 1621230378707, + "joinType": 0, + "muteType": 0 + }, + { + "id": 127067012814868480, + "markId": "62025607", + "name": "test-group-1", + "avatar": "", + "introduce": "", + "owner": { + "memberId": "1FKxgaEh5fuSm7a35BfUnKYAmradowpiTR", + "memberName": "", + "memberType": 2 + }, + "memberNum": 5, + "maximum": 200, + "status": 0, + "createTime": 1621235936650, + "joinType": 0, + "muteType": 0 + }, + { + "id": 127082931377147904, + "markId": "88951481", + "name": "test-group-1", + "avatar": "", + "introduce": "", + "owner": { + "memberId": "1FKxgaEh5fuSm7a35BfUnKYAmradowpiTR", + "memberName": "", + "memberType": 2 + }, + "memberNum": 11, + "maximum": 200, + "status": 0, + "createTime": 1621239731935, + "joinType": 0, + "muteType": 0 + }, + { + "id": 127100214044528640, + "markId": "43833969", + "name": "test-group-1", + "avatar": "", + "introduce": "", + "owner": { + "memberId": "1FKxgaEh5fuSm7a35BfUnKYAmradowpiTR", + "memberName": "", + "memberType": 2 + }, + "memberNum": 3, + "maximum": 200, + "status": 0, + "createTime": 1621243852439, + "joinType": 0, + "muteType": 0 + }, + { + "id": 127485592593240064, + "markId": "00865442", + "name": "test-group-1", + "avatar": "", + "introduce": "", + "owner": { + "memberId": "1FKxgaEh5fuSm7a35BfUnKYAmradowpiTR", + "memberName": "", + "memberType": 2 + }, + "memberNum": 9, + "maximum": 200, + "status": 0, + "createTime": 1621335733857, + "joinType": 0, + "muteType": 0 + } + ] + } +} +``` + + + + + +### 群成员列表+ + +`POST` URL: /app/group-member-list + + + +**Herder** + +`FZM-SIGNATURE` = token + + + +**请求参数:** + +| 参数名 | 必选 | 类型 | 说明 | +| ------ | ---- | ----- | ---- | +| id | true | int64 | 群ID | + + + +**请求参数示例** + +```json +{ + "id": 127082931377147904 +} +``` + + + +**返回参数(data):** + +| 参数名 | 类型 | 说明 | +| ------- | ------------ | --------------- | +| id | int | 群id | +| members | []MemberInfo | 全部群成员 list | + + + +**MemberInfo 参数类型** + +| 参数名 | 类型 | 说明 | +| ---------- | ------ | ------------------------------------------ | +| memberId | string | 用户id | +| memberName | string | 用户群昵称 | +| memberType | int | 用户角色,0=群主,1=管理员,2=群员,3=退群 | + + + +**返回参数示例:** + +```json +{ + "result": 0, + "message": "", + "data": { + "id": 127043701116506112, + "members": [ + { + "memberId": "1FKxgaEh5fuSm7a35BfUnKYAmradowpiTR", + "memberName": "", + "memberType": 2 + }, + { + "memberId": "member-1", + "memberName": "", + "memberType": 0 + }, + { + "memberId": "member-2", + "memberName": "", + "memberType": 0 + }, + { + "memberId": "member-5", + "memberName": "", + "memberType": 0 + }, + { + "memberId": "member-6", + "memberName": "", + "memberType": 0 + } + ] + } +} +``` + + + + + +### 群成员信息+ + +URL: /app/group-member-info + +`GET` + + + +**Herder** + +`FZM-SIGNATURE` = token + + + +**请求参数:** + +| 参数名 | 必选 | 类型 | 说明 | +| -------- | ---- | ------ | --------- | +| id | true | int | 群ID | +| memberId | true | string | 群成员 ID | + + + +**请求参数示例** + +```json +{ + "id": 125290793882619904, + "memberId": "123" +} +``` + + + +**返回参数(data):** + +| 参数名 | 类型 | 说明 | +| ---------- | ---------- | ------ | +| id | int | 群id | +| newMembers | MemberInfo | 群成员 | + + + +**MemberInfo 参数类型** + +| 参数名 | 类型 | 说明 | +| ---------- | ------ | ------------------------------------------ | +| memberId | string | 用户id | +| memberName | string | 用户群昵称 | +| memberType | int | 用户角色,0=群主,1=管理员,2=群员,3=退群 | + + + +**返回参数示例:** + +```json +{ + "result": 0, + "message": "", + "data": { + "memberId": "member-1", + "memberName": "", + "memberType": 0 + } +} +``` + + + + +### 踢人+ + +`POST` URL: /app/group-remove + + + +### 退群+ + +`POST` URL:/app/group-exit + + + +### 解散群 + +URL: /disband-group + +`PUT` + + + + +### 更新群头像 + +URL: /update-group-avatar + +`PUT` + + + +### 更新群名称 + +URL: /update-group-name + +`PUT` + + + +### 更新个人群昵称(自己改自己的, 群主和管理员改所有人) + +URL: /update-group-member-name + +`PUT` + + + +### 更新群简介 + +URL: /update-group-introduce + +`PUT` + + + +### 更新加群权限设置 + +URL: /update-group-join-type + +`PUT` + + + + + +### 更新群成员类型 + +URL: /update-group-member-type + +`PUT` + + + +### 转移群给群成员 + +URL: /update-group-owner + +`PUT` + + + + + + + +### 更新群状态 + +URL: /update-group-status + +`PUT` + + + +### 更新群成员上限 + +URL: /update-group-maximum + +`PUT` \ No newline at end of file diff --git a/doc/ossApi.md b/doc/ossApi.md new file mode 100644 index 0000000..2f09719 --- /dev/null +++ b/doc/ossApi.md @@ -0,0 +1,60 @@ +# 对象存储服务 +### 获取token +URL: /oss/get-token + +`get`|`post` + +**请求参数:** +无 + +**返回参数:** + +```json +{ + "result": 0, + "message": "操作成功", + "data": { + "RequestId": "E7A855E3-0A3B-479A-BF22-416A2D5D27B1", + "Credentials": { + "AccessKeySecret": "BxbYXWGjKrVT4qSzi6ZoLtD1qjZLAfNaULreCMb7EsYm", + "Expiration": "2021-02-24T07:48:21Z", + "AccessKeyId": "STS.NUrf45xSfTX7EkJxoo3G7C9Cm", + "SecurityToken": "CAIS8wF1q6Ft5B2yfSjIr5bHLY6BlYxH45rcR037nG86P8gbrPzojzz2IHhNfXlqBeket/g1n2hY5/oelqN1TIVATEiBZNNotm32E/M0Jdivgde8yJBZor/HcDHhJnyW9cvWZPqDP7G5U/yxalfCuzZuyL/hD1uLVECkNpv74vwOLK5gPG+CYCFBGc1dKyZ7tcYeLgGxD/u2NQPwiWeiZygB+CgE0DkhtfTvkp3Ht0OP1wOhk9V4/dqhfsKWCOB3J4p6XtuP2+h7S7HMyiY46WIRrP4v3PYYoG+e4ovGWwkAv0mcUezT6NhmKEpwfrAq7DDiSR5ZYpcagAEcYDFBCF3QY2aQ8Jcs9L4utLb7Z6JdPvyG/zF7l7P1Br0MX0ad6sgWEGWTvTuxemRtmKSWxMEivRzx+6aT515Y7OQdFyX1uLrR1YOq9R2tjysh2d54wu09qr7BhPavADSFHj8fCrZugoul6vHtoslRgL+KSpuI3buhZ08VDxh5rg==" + }, + "AssumedRoleUser": { + "AssumedRoleId": "301182410142308944:normal-app", + "Arn": "acs:ram::1264888835193631:role/normal-app/normal-app" + } + } +} +``` + +### 获取华为云token +URL: /oss/get-huaweiyun-token + +`get`|`post` + +**请求参数:** +无 + +**返回参数:** + +```json +{ + "result": 0, + "message": "操作成功", + "data": { + "RequestId": "", + "Credentials": { + "AccessKeySecret": "BxbYXWGjKrVT4qSzi6ZoLtD1qjZLAfNaULreCMb7EsYm", + "Expiration": "2021-02-24T07:48:21Z", + "AccessKeyId": "NUrf45xSfTX7EkJxoo3G7C9Cm", + "SecurityToken": "CAIS8wF1q6Ft5B2yfSjIr5bHLY6BlYxH45rcR037nG86P8gbrPzojzz2IHhNfXlqBeket/g1n2hY5/oelqN1TIVATEiBZNNotm32E/M0Jdivgde8yJBZor/HcDHhJnyW9cvWZPqDP7G5U/yxalfCuzZuyL/hD1uLVECkNpv74vwOLK5gPG+CYCFBGc1dKyZ7tcYeLgGxD/u2NQPwiWeiZygB+CgE0DkhtfTvkp3Ht0OP1wOhk9V4/dqhfsKWCOB3J4p6XtuP2+h7S7HMyiY46WIRrP4v3PYYoG+e4ovGWwkAv0mcUezT6NhmKEpwfrAq7DDiSR5ZYpcagAEcYDFBCF3QY2aQ8Jcs9L4utLb7Z6JdPvyG/zF7l7P1Br0MX0ad6sgWEGWTvTuxemRtmKSWxMEivRzx+6aT515Y7OQdFyX1uLrR1YOq9R2tjysh2d54wu09qr7BhPavADSFHj8fCrZugoul6vHtoslRgL+KSpuI3buhZ08VDxh5rg==" + }, + "AssumedRoleUser": { + "AssumedRoleId": "", + "Arn": "" + } + } + } +``` \ No newline at end of file diff --git a/doc/proto.md b/doc/proto.md new file mode 100644 index 0000000..720a871 --- /dev/null +++ b/doc/proto.md @@ -0,0 +1,248 @@ +# 应用方消息子协议 + +**协议格式:** + +| 参数名 | 必选 | 类型 | 说明 | +| :----- | :--- | :--- | :--- | +|eventType | true | int32 | 事件类型 | +|body | true | byte[] | 消息体 | + +## 事件类型 +| 类型 | 说明 | +| :----- | :--- | +| 0 | 普通消息 | +| 1 | 消息回复 | +| 2 | 通知信令 | + + +## 普通消息 +**body描述(encode:proto序列化)** + +| 参数名 | 必选 | 类型 | 说明 | +| :----- | :--- | :--- | :--- | +|channelType | true | int32 | 消息通道 | +|logId | true | int64 | 消息id | +|msgId | true | string | 客户端序列 | +|from | true | string | 发送者 | +|target | true | string | 接收者 | +|msgType | true | int32 | 消息类型 | +|msg | true | binary | 消息体 | +|datetime | true | uint64 | 事件戳,ms | +|source | true | Source | 来源,详见如下 | + +Source结构: +``` +message Source { + int32 channelType=1; + SourceUser from=2; + SourceUser target=3; +} + +message SourceUser { + string id=1; + string name=2; +} +``` + +### 消息通道 +| 类型 | 说明 | +| :----- | :--- | +| 0 | 单聊 | +| 1 | 群聊 | + +### 消息类型 +| 类型 | 说明 | +| :----- | :--- | +| 0 | 系统消息 | +| 1 | 文本消息 | +| 2 | 音频消息 | +| 3 | 图片消息 | +| 4 | 视频消息 | +| 5 | 文件消息 | +| 6 | 卡片消息 | +| 7 | 通知消息 | +| 8 | 合并转发 | + +注意:msg 必须可以反序列化为msgType相对应的结构体 +具体协议参考 `pkg/proto/api.proto` + +``` +message TextMsg { + string content = 1; + repeated string mentions = 2; +} + +message AudioMsg { + string mediaUrl = 1; + int32 time = 2; +} + +message ImageMsg { + string mediaUrl = 1; + int32 height = 2; + int32 width = 3; +} + +message VideoMsg { + string mediaUrl = 1; + int32 time = 2; + int32 height = 3; + int32 width = 4; +} + +message FileMsg { + string mediaUrl = 1; + string name = 2; + string md5 = 3; + int64 size = 4; +} + +message CardMsg { + string bank = 1; + string name = 2; + string account = 3; +} + +message NoticeMsg { + AlertType type = 1; + bytes body = 2; +} + +message ForwardMsg { + repeated ForwardItem items = 1; +} + +message ForwardItem { + string avatar=1; + string name=2; + int32 msgType=3; + bytes msg=4; + uint64 datetime=5; +} +``` + +#### 通知消息 +##### 通知类型 +| 类型 | 说明 | +| :----- | :--- | +| 0 | 修改群名 | +| 1 | 加群 | +| 2 | 退群 | +| 3 | 踢群 | +| 4 | 删群 | +| 5 | 群禁言模式更改 | +| 6 | 更改禁言名单 | + +注意:body必须可以反序列化为AlertType相对应的结构体 +具体协议参考 `pkg/proto/api.proto` + +``` +message AlertUpdateGroupName { + int64 group = 1; + string operator = 2; + string name = 3; +} + +message AlertSignInGroup { + int64 group = 1; + string inviter = 2; + repeated string members = 3; +} + +message AlertSignOutGroup { + int64 group = 1; + string operator = 2; +} + +message AlertKickOutGroup { + int64 group = 1; + string operator = 2; + repeated string members = 3; +} + +message AlertDeleteGroup { + int64 group = 1; + string operator = 2; +} + +message AlertUpdateGroupMuted { + int64 group = 1; + string operator = 2; + MuteType type = 3; +} + +message AlertUpdateGroupMemberMutedTime { + int64 group = 1; + string operator = 2; + repeated string members = 3; +} +``` + +### Receive_Reply协议格式 +| 参数名 | 必选 | 类型 | 说明 | +| :----- | :--- | :--- | :--- | +|eventType | true | int32 | 事件类型 | +|body | true | byte[] | 消息体 | + +当eventType为0时,body必须可以被反序列化为CommonMsgAck +``` +message CommonMsgAck { + int64 logId = 2; + uint64 datetime = 8; +} +``` + +## 通知信令 +**body描述(encode:proto序列化)** + +| 参数名 | 必选 | 类型 | 说明 | +| :----- | :--- | :--- | :--- | +|action | true | ActionType | 信令类型 | +|body | true | binary | 消息体 | +``` +//alert msg define +message NotifyMsg { + ActionType action = 1; + bytes body = 2; +} +``` +### 信令类型 +| 类型 | 说明 | +| :----- | :--- | +| 0 | 送达 | +| 1 | 加群 | +| 2 | 退群 | +| 3 | 删群 | +| 20 | 更新加群权限 | +| 21 | 更新群加好友权限 | +| 22 | 更新群禁言类型 | +| 23 | 更新群成员 | +| 24 | 更新禁言列表 | +| 25 | 更新群名 | +| 26 | 更新群头像 | + +注意:body必须可反序列化为ActionType对应的结构体。 +具体参考 `pkg/proto/api.proto` + +``` +message ActionReceived { + repeated uint64 logs = 1; +} + +message ActionSignInGroup { + repeated string uid = 1; + int64 group = 2; + uint64 time = 3; +} + +message ActionSignOutGroup { + repeated string uid = 1; + int64 group = 2; + uint64 time = 3; +} + +message ActionDeleteGroup { + int64 group = 1; + uint64 time = 2; +} +``` \ No newline at end of file diff --git a/doc/recordApi.md b/doc/recordApi.md new file mode 100644 index 0000000..ce3e231 --- /dev/null +++ b/doc/recordApi.md @@ -0,0 +1,33 @@ +# 找回服务 +### 发送消息 +URL: /record/push2 + +`post` +**Request Headers** +- Content-Type: multipart/form-data + +**Body:**`form-data` + +| 参数名 | 必选 | 类型 | 说明 | +| ------ | ---- | ------ | ------ | +| name | true | string | 区号 | +| filename | true | string | 手机号 | + +**请求示例** +``` +请求头: + Content-Type=multipart/form-data +请求参数: + name="message" + filename="message" +``` + +**返回参数:** +```json +{ + "result": 0, + "message": "操作成功", + "data": { + } +} +``` \ No newline at end of file diff --git a/doc/root.md b/doc/root.md new file mode 100644 index 0000000..085bfc4 --- /dev/null +++ b/doc/root.md @@ -0,0 +1,11 @@ +# 总览 +## 约定 +- 数据类型`datetime`:时间戳,毫秒(ms) +- 所有 `http` 接口请求都是 `POST` 方法, 数据请求和返回结果 都是 `JSON` 格式 +- Header: 接口里需要的 + - `FZM-SIGNATURE`(地址):格式[##]; signature格式:[*] + - `FZM-DEVICE`(设备类型):枚举 iOS、Android、PC + - `FZM-UUID`(设备mac) + - `FZM-DEVICE-NAME`(设备名称) + - `FZM-VERSION` (应用版本号) +- 返回结果的 `result` 为 `0` 代表成功,`data` 里面是具体数据,`result` 不为 `0` 代表失败,`message` 为失败信息 \ No newline at end of file diff --git a/gateway/api/READMD.md b/gateway/api/READMD.md new file mode 100644 index 0000000..18a1f1a --- /dev/null +++ b/gateway/api/READMD.md @@ -0,0 +1,32 @@ +# Gateway + +业务网关层往往又被称作业务聚合层,该层由若干个业务服务构成,负责接收通用网关转发过来的流量。核心作用如下: + +### 1. 路由管理 + +业务网关层的服务负责自己服务的路由表维护。 + +### 2. 参数校验 + +业务网关层的服务负责执行与客户端约定的参数校验,验证通过之后再组装成后端微服务需要的数据结构请求到后端。 + +### 3. 权限校验 + +各个业务网关层的服务通过底层的用户服务调用来实现权限校验,对于哪些路由需要权限校验,哪些路由不需要,完全由业务网关层的服务自行维护。 + +### 4. 接口聚合 + +业务网关层的服务可能需要调用多个后端的微服务来组合实现一个接口,根据自身需求来对下层返回的数据进行聚合和处理。 + +### 5. 协议转换 + +业务网关层的服务接收转发过来的HTTP请求,并转换为内部的一个或者多个GRPC微服务调用来实现接口逻辑。 + +### 6. 数据转换 + +业务网关层的服务的输入和输出数据结构必须是表示层需要的,因此它所负责的数据结构和后端GRPC微服务的数据结构会不一样。业务网关层的服务需要负责数据结构的转换和封装处理。 + +> gateway 里不应该有复杂业务逻辑 +> + +文档路径 : http://172.16.101.107:8888/swagger/index.html \ No newline at end of file diff --git a/gateway/api/v1/CHANGELOG.md b/gateway/api/v1/CHANGELOG.md new file mode 100644 index 0000000..a468a7e --- /dev/null +++ b/gateway/api/v1/CHANGELOG.md @@ -0,0 +1,45 @@ +版本号`major.minor.patch`具体规则如下: + +- major:主版本号,如有重大版本重构则该字段递增,通常各主版本间接口不兼容。 +- minor:次版本号,各次版本号间接口保持兼容,如有接口新增或优化则该字段递增。 +- patch:补丁号,如有功能改善或缺陷修复则该字段递增。 + +## version 0.1.0 2022_01_20 + +**Feature** + +- 增加 `group` 相关 api 接口 + +## version 0.0.8 + +**Feature** + +- 更新gateway下服务修改signal和noticemsg名称 2021_12_07_17_52_18 + +## version 0.0.7 + +**配置文件新增** + +所有 `[xxxRPCClient]` 增加 `RegAddrs = "127.0.0.1:2379"` 字段 + +**Feature** +- 更新 etcdv3.5.0 v0.0.7 2021.11.03 + +## version 0.0 + +**Feature** +- 模块开启接口 @v0.0.2 +- 撤回消息 @v0.0.4 +- 改用 imparse 中的 proto @v0.0.5 2021.10.14 +- 新增撤回消息时限配置文件 v0.0.6 2021.10.22 + + +## example x.x.x @yy.mm.dd + +**Feature** + +**Bug Fixes** + +**Improvement** + +**Breaking Change** diff --git a/gateway/api/v1/Makefile b/gateway/api/v1/Makefile new file mode 100644 index 0000000..5cf6ead --- /dev/null +++ b/gateway/api/v1/Makefile @@ -0,0 +1,48 @@ +# golang1.9 or latest +# 1. make help +# 2. make dep +# 3. make build +# ... + +VERSION := $(shell echo $(shell cat gateway.go | grep "projectVersion =" | cut -d '=' -f2)) +APP_NAME := gateway +BUILD_DIR := build +APP := ${BUILD_DIR}/${APP_NAME}_v${VERSION} +PKG_NAME := ${APP_NAME}_v${VERSION} +PKG := ${PKG_NAME}.tar.gz + +main_path = "main" +go_version = $(shell go version | awk '{ print $3 }') +build_time = $(shell date "+%Y-%m-%d %H:%M:%S %Z") +git_commit = $(shell git rev-parse --short=10 HEAD) +flags := -ldflags "-X '${main_path}.goVersion=${go_version}' \ +-X '${main_path}.buildTime=${build_time}' \ +-X '${main_path}.gitCommit=${git_commit}' \ +-X 'google.golang.org/protobuf/reflect/protoregistry.conflictPolicy=warn'" + +.PHONY: clean build pkg + +swag: + @echo '┌ start gen swag' + @swag init -g internal/handler/routes.go + @echo '└ end gen swag' + +clean: ## Remove previous build + @rm -rf ${BUILD_DIR} + @go clean + +build: swag #checkgofmt ## Build the binary file + GOOS=linux GOARCH=amd64 GO111MODULE=on GOPROXY=https://goproxy.cn,direct GOSUMDB="sum.golang.google.cn" go build -v $(flags) -o $(APP) + +pkg: build ## Package + mkdir -p ${PKG_NAME}/bin + mkdir -p ${PKG_NAME}/etc + cp ${APP} ${PKG_NAME}/bin/ + cp etc/* ${PKG_NAME}/etc/ + tar zvcf ${PKG} ${PKG_NAME} + rm -rf ${PKG_NAME} + +REMOTE_BIN_PATH := /opt/dtalk/srv/app/bin +upload: build + rsync -r $(APP) 107:$(REMOTE_BIN_PATH) + ssh 107 "cd $(REMOTE_BIN_PATH) && chmod 777 $(PKG_NAME) && ln -sf $(PKG_NAME) $(APP_NAME) && supervisorctl restart $(APP_NAME)" \ No newline at end of file diff --git a/gateway/api/v1/docs/docs.go b/gateway/api/v1/docs/docs.go new file mode 100644 index 0000000..993bc92 --- /dev/null +++ b/gateway/api/v1/docs/docs.go @@ -0,0 +1,2330 @@ +// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT +// This file was generated by swaggo/swag + +package docs + +import ( + "bytes" + "encoding/json" + "strings" + + "github.com/alecthomas/template" + "github.com/swaggo/swag" +) + +var doc = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{.Description}}", + "title": "{{.Title}}", + "contact": {}, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/app/group-list": { + "post": { + "tags": [ + "group 群信息" + ], + "summary": "查询群列表", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.GetGroupListReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResp" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.GetGroupListResp" + } + } + } + ] + } + } + } + } + }, + "/app/modules/all": { + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "startup 初始化模块" + ], + "summary": "获取模块启用状态", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/model.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/model.GetModuleResp" + } + } + } + } + ] + } + } + } + } + }, + "/app/mute-list": { + "post": { + "tags": [ + "group 禁言" + ], + "summary": "查询群内被禁言成员名单", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.GetMuteListReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResp" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.GetMuteListResp" + } + } + } + ] + } + } + } + } + }, + "/app/pri-chat-record": { + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "record 消息模块" + ], + "summary": "获得聊天记录", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/model.GetPriRecordsReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/model.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.GetPriRecordsResp" + } + } + } + ] + } + } + } + } + }, + "/app/record/focus": { + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "record 消息模块" + ], + "summary": "关注消息", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/model.FocusMsgReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/model.GeneralResponse" + } + } + } + } + }, + "/app/record/revoke": { + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "record 消息模块" + ], + "summary": "撤回消息", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/model.RevokeMsgReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/model.GeneralResponse" + } + } + } + } + }, + "/group/app/avatar": { + "post": { + "tags": [ + "group 群信息" + ], + "summary": "更新群头像", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.UpdateGroupAvatarReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResp" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.UpdateGroupAvatarResp" + } + } + } + ] + } + } + } + } + }, + "/group/app/change-owner": { + "post": { + "tags": [ + "group 群动作" + ], + "summary": "转让群", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.ChangeOwnerReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResp" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.ChangeOwnerResp" + } + } + } + ] + } + } + } + } + }, + "/group/app/create-group": { + "post": { + "tags": [ + "group 群动作" + ], + "summary": "创建群", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.CreateGroupReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResp" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.CreateGroupResp" + } + } + } + ] + } + } + } + } + }, + "/group/app/friendType": { + "post": { + "tags": [ + "group 群信息" + ], + "summary": "更新群内加好友设置", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.UpdateGroupFriendTypeReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResp" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.UpdateGroupFriendTypeResp" + } + } + } + ] + } + } + } + } + }, + "/group/app/group-disband": { + "post": { + "tags": [ + "group 群动作" + ], + "summary": "解散群", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.GroupDisbandReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResp" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.GroupDisbandResp" + } + } + } + ] + } + } + } + } + }, + "/group/app/group-exit": { + "post": { + "tags": [ + "group 群动作" + ], + "summary": "退群", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.GroupExitReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResp" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.GroupExitResp" + } + } + } + ] + } + } + } + } + }, + "/group/app/group-info": { + "post": { + "tags": [ + "group 群信息" + ], + "summary": "查询群信息", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.GetGroupInfoReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResp" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.GetGroupInfoResp" + } + } + } + ] + } + } + } + } + }, + "/group/app/group-member-info": { + "post": { + "tags": [ + "group 群成员信息" + ], + "summary": "查询群成员信息", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.GetGroupMemberInfoReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResp" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.GetGroupMemberInfoResp" + } + } + } + ] + } + } + } + } + }, + "/group/app/group-member-list": { + "post": { + "tags": [ + "group 群成员信息" + ], + "summary": "查询群成员列表", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.GetGroupMemberListReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResp" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.GetGroupMemberListResp" + } + } + } + ] + } + } + } + } + }, + "/group/app/group-pub-info": { + "post": { + "tags": [ + "group 群信息" + ], + "summary": "查询群公开信息", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.GetGroupPubInfoReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResp" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.GetGroupPubInfoResp" + } + } + } + ] + } + } + } + } + }, + "/group/app/group-remove": { + "post": { + "tags": [ + "group 群动作" + ], + "summary": "踢人", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.GroupRemoveReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResp" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.GroupRemoveResp" + } + } + } + ] + } + } + } + } + }, + "/group/app/invite-group-members": { + "post": { + "tags": [ + "group 群动作" + ], + "summary": "邀请新群员", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.InviteGroupMembersReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResp" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.InviteGroupMembersResp" + } + } + } + ] + } + } + } + } + }, + "/group/app/join-group": { + "post": { + "tags": [ + "group 群动作" + ], + "summary": "直接进群", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.JoinGroupReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResp" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.JoinGroupResp" + } + } + } + ] + } + } + } + } + }, + "/group/app/joinType": { + "post": { + "tags": [ + "group 群信息" + ], + "summary": "更新群内加好友设置", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.UpdateGroupJoinTypeReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResp" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.UpdateGroupJoinTypeResp" + } + } + } + ] + } + } + } + } + }, + "/group/app/member/muteTime": { + "post": { + "tags": [ + "group 禁言" + ], + "summary": "更新群成员禁言时间", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.UpdateGroupMemberMuteTimeReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResp" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.UpdateGroupMemberMuteTimeResp" + } + } + } + ] + } + } + } + } + }, + "/group/app/member/name": { + "post": { + "tags": [ + "group 群成员信息" + ], + "summary": "更新群成员昵称", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.UpdateGroupMemberNameReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResp" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.UpdateGroupMemberNameResp" + } + } + } + ] + } + } + } + } + }, + "/group/app/member/type": { + "post": { + "tags": [ + "group 群成员信息" + ], + "summary": "设置管理员", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.SetAdminReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResp" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.SetAdminResp" + } + } + } + ] + } + } + } + } + }, + "/group/app/muteType": { + "post": { + "tags": [ + "group 群信息" + ], + "summary": "更新群内加好友设置", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.UpdateGroupMuteTypeReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResp" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.UpdateGroupMuteTypeResp" + } + } + } + ] + } + } + } + } + }, + "/group/app/name": { + "post": { + "tags": [ + "group 群信息" + ], + "summary": "更新群名称", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.UpdateGroupNameReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResp" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.UpdateGroupNameResp" + } + } + } + ] + } + } + } + } + }, + "/record/push": { + "post": { + "description": "comet.Proto由接口组装,客户端只需传入comet.Proto的body部分", + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "tags": [ + "record 消息模块" + ], + "summary": "推送消息", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "消息协议序列化", + "name": "message", + "in": "body", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/model.GeneralResponse" + } + } + } + } + }, + "/record/push2": { + "post": { + "description": "comet.Proto由客户端传入", + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "tags": [ + "record 消息模块" + ], + "summary": "推送消息2", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "消息协议序列化", + "name": "message", + "in": "body", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/model.GeneralResponse" + } + } + } + } + }, + "/user/login": { + "post": { + "description": "内部接口,comet层使用", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "account 账户模块" + ], + "summary": "用户登录", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/model.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.AddressLoginResp" + } + } + } + ] + } + } + } + } + } + }, + "definitions": { + "model.AddressLoginResp": { + "type": "object", + "properties": { + "address": { + "description": "用户地址", + "type": "string", + "example": "123" + } + } + }, + "model.FocusMsgReq": { + "type": "object", + "required": [ + "logId" + ], + "properties": { + "logId": { + "type": "integer" + }, + "type": { + "type": "integer", + "enum": [ + 0, + 1 + ] + } + } + }, + "model.GeneralResponse": { + "type": "object", + "properties": { + "data": { + "type": "object" + }, + "message": { + "type": "string" + }, + "result": { + "type": "integer" + } + } + }, + "model.GetModuleResp": { + "type": "object", + "properties": { + "endPoints": { + "type": "array", + "items": { + "type": "string" + } + }, + "isEnabled": { + "type": "boolean" + }, + "name": { + "type": "string", + "enum": [ + "wallet", + "oa", + "redpacket" + ] + } + } + }, + "model.GetPriRecordsReq": { + "type": "object", + "required": [ + "count", + "targetId" + ], + "properties": { + "count": { + "description": "消息数量", + "type": "integer" + }, + "logId": { + "description": "消息 ID", + "type": "string" + }, + "targetId": { + "description": "接受者 ID", + "type": "string" + } + } + }, + "model.GetPriRecordsResp": { + "type": "object", + "properties": { + "record_count": { + "description": "聊天记录数量", + "type": "integer" + }, + "records": { + "description": "聊天记录", + "type": "array", + "items": { + "$ref": "#/definitions/model.Record" + } + } + } + }, + "model.Record": { + "type": "object", + "properties": { + "content": { + "description": "消息内容", + "type": "object" + }, + "createTime": { + "description": "消息发送时间", + "type": "integer" + }, + "fromId": { + "description": "发送者 id", + "type": "string" + }, + "logId": { + "description": "log id", + "type": "string" + }, + "msgId": { + "description": "msg id (uuid)", + "type": "string" + }, + "msgType": { + "description": "消息类型", + "type": "integer" + }, + "targetId": { + "description": "接收者 id", + "type": "string" + } + } + }, + "model.RevokeMsgReq": { + "type": "object", + "required": [ + "logId" + ], + "properties": { + "logId": { + "type": "integer" + }, + "type": { + "type": "integer", + "enum": [ + 0, + 1 + ] + } + } + }, + "types.ChangeOwnerReq": { + "type": "object", + "required": [ + "memberId" + ], + "properties": { + "id": { + "description": "群 ID", + "type": "integer" + }, + "idStr": { + "description": "如果同时填了 idStr, 则优先选择 idStr", + "type": "string" + }, + "memberId": { + "description": "被转让为群主的群成员 ID", + "type": "string" + } + } + }, + "types.ChangeOwnerResp": { + "type": "object" + }, + "types.CreateGroupReq": { + "type": "object", + "properties": { + "avatar": { + "type": "string" + }, + "introduce": { + "type": "string" + }, + "memberIds": { + "type": "array", + "items": { + "type": "string" + } + }, + "name": { + "type": "string" + } + } + }, + "types.CreateGroupResp": { + "type": "object", + "properties": { + "adminNum": { + "description": "群内管理员数量", + "type": "integer" + }, + "avatar": { + "description": "头像 url", + "type": "string" + }, + "createTime": { + "description": "群创建时间", + "type": "integer" + }, + "friendType": { + "description": "加好友限制, 0=群内可加好友,1=群内禁止加好友", + "type": "integer" + }, + "groupType": { + "description": "群类型 (0: 普通群, 1: 全员群, 2: 部门群)", + "type": "integer" + }, + "id": { + "description": "群 ID", + "type": "integer" + }, + "idStr": { + "type": "string" + }, + "introduce": { + "type": "string" + }, + "joinType": { + "description": "加群方式,0=无需审批(默认),1=禁止加群,群主和管理员邀请加群, 2=普通人邀请需要审批,群主和管理员直接加群", + "type": "integer" + }, + "key": { + "type": "string" + }, + "markId": { + "description": "群显示的 ID", + "type": "string" + }, + "maximum": { + "description": "群人数上限", + "type": "integer" + }, + "memberNum": { + "description": "群人数", + "type": "integer" + }, + "members": { + "description": "群成员", + "type": "array", + "items": { + "$ref": "#/definitions/types.GroupMember" + } + }, + "muteNum": { + "description": "群内当前被禁言的人数", + "type": "integer" + }, + "muteType": { + "description": "禁言, 0=全员可发言, 1=全员禁言(除群主和管理员)", + "type": "integer" + }, + "name": { + "description": "群名称 加密的", + "type": "string" + }, + "owner": { + "description": "群主 信息", + "$ref": "#/definitions/types.GroupMember" + }, + "person": { + "description": "本人在群内的信息", + "$ref": "#/definitions/types.GroupMember" + }, + "publicName": { + "description": "公开的群名称 不加密的", + "type": "string" + }, + "status": { + "description": "群状态,0=正常 1=封禁 2=解散", + "type": "integer" + } + } + }, + "types.GeneralResp": { + "type": "object", + "properties": { + "data": { + "type": "object" + }, + "message": { + "type": "integer" + }, + "result": { + "type": "integer" + } + } + }, + "types.GetGroupInfoReq": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "idStr": { + "description": "如果同时填了 idStr, 则优先选择 idStr", + "type": "string" + } + } + }, + "types.GetGroupInfoResp": { + "type": "object", + "properties": { + "adminNum": { + "description": "群内管理员数量", + "type": "integer" + }, + "avatar": { + "description": "头像 url", + "type": "string" + }, + "createTime": { + "description": "群创建时间", + "type": "integer" + }, + "friendType": { + "description": "加好友限制, 0=群内可加好友,1=群内禁止加好友", + "type": "integer" + }, + "groupType": { + "description": "群类型 (0: 普通群, 1: 全员群, 2: 部门群)", + "type": "integer" + }, + "id": { + "description": "群 ID", + "type": "integer" + }, + "idStr": { + "type": "string" + }, + "introduce": { + "type": "string" + }, + "joinType": { + "description": "加群方式,0=无需审批(默认),1=禁止加群,群主和管理员邀请加群, 2=普通人邀请需要审批,群主和管理员直接加群", + "type": "integer" + }, + "key": { + "type": "string" + }, + "markId": { + "description": "群显示的 ID", + "type": "string" + }, + "maximum": { + "description": "群人数上限", + "type": "integer" + }, + "memberNum": { + "description": "群人数", + "type": "integer" + }, + "members": { + "type": "array", + "items": { + "$ref": "#/definitions/types.GroupMember" + } + }, + "muteNum": { + "description": "群内当前被禁言的人数", + "type": "integer" + }, + "muteType": { + "description": "禁言, 0=全员可发言, 1=全员禁言(除群主和管理员)", + "type": "integer" + }, + "name": { + "description": "群名称 加密的", + "type": "string" + }, + "owner": { + "description": "群主 信息", + "$ref": "#/definitions/types.GroupMember" + }, + "person": { + "description": "本人在群内的信息", + "$ref": "#/definitions/types.GroupMember" + }, + "publicName": { + "description": "公开的群名称 不加密的", + "type": "string" + }, + "status": { + "description": "群状态,0=正常 1=封禁 2=解散", + "type": "integer" + } + } + }, + "types.GetGroupListReq": { + "type": "object" + }, + "types.GetGroupListResp": { + "type": "object", + "properties": { + "groups": { + "type": "array", + "items": { + "$ref": "#/definitions/types.GroupInfo" + } + } + } + }, + "types.GetGroupMemberInfoReq": { + "type": "object", + "required": [ + "memberId" + ], + "properties": { + "id": { + "type": "integer" + }, + "idStr": { + "description": "如果同时填了 idStr, 则优先选择 idStr", + "type": "string" + }, + "memberId": { + "type": "string" + } + } + }, + "types.GetGroupMemberInfoResp": { + "type": "object", + "properties": { + "memberId": { + "description": "用户 ID", + "type": "string" + }, + "memberMuteTime": { + "description": "该用户被禁言结束的时间 9223372036854775807=永久禁言", + "type": "integer" + }, + "memberName": { + "description": "用户群昵称", + "type": "string" + }, + "memberType": { + "description": "用户角色,2=群主,1=管理员,0=群员,10=退群", + "type": "integer" + } + } + }, + "types.GetGroupMemberListReq": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "idStr": { + "description": "如果同时填了 idStr, 则优先选择 idStr", + "type": "string" + } + } + }, + "types.GetGroupMemberListResp": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "idStr": { + "description": "如果同时填了 idStr, 则优先选择 idStr", + "type": "string" + }, + "members": { + "type": "array", + "items": { + "$ref": "#/definitions/types.GroupMember" + } + } + } + }, + "types.GetGroupPubInfoReq": { + "type": "object", + "properties": { + "id": { + "description": "群 ID", + "type": "integer" + }, + "idStr": { + "description": "如果同时填了 idStr, 则优先选择 idStr", + "type": "string" + } + } + }, + "types.GetGroupPubInfoResp": { + "type": "object", + "properties": { + "adminNum": { + "description": "群内管理员数量", + "type": "integer" + }, + "avatar": { + "description": "头像 url", + "type": "string" + }, + "createTime": { + "description": "群创建时间", + "type": "integer" + }, + "friendType": { + "description": "加好友限制, 0=群内可加好友,1=群内禁止加好友", + "type": "integer" + }, + "groupType": { + "description": "群类型 (0: 普通群, 1: 全员群, 2: 部门群)", + "type": "integer" + }, + "id": { + "description": "群 ID", + "type": "integer" + }, + "idStr": { + "type": "string" + }, + "introduce": { + "type": "string" + }, + "joinType": { + "description": "加群方式,0=无需审批(默认),1=禁止加群,群主和管理员邀请加群, 2=普通人邀请需要审批,群主和管理员直接加群", + "type": "integer" + }, + "key": { + "type": "string" + }, + "markId": { + "description": "群显示的 ID", + "type": "string" + }, + "maximum": { + "description": "群人数上限", + "type": "integer" + }, + "memberNum": { + "description": "群人数", + "type": "integer" + }, + "muteNum": { + "description": "群内当前被禁言的人数", + "type": "integer" + }, + "muteType": { + "description": "禁言, 0=全员可发言, 1=全员禁言(除群主和管理员)", + "type": "integer" + }, + "name": { + "description": "群名称 加密的", + "type": "string" + }, + "owner": { + "description": "群主 信息", + "$ref": "#/definitions/types.GroupMember" + }, + "person": { + "description": "本人在群内的信息", + "$ref": "#/definitions/types.GroupMember" + }, + "publicName": { + "description": "公开的群名称 不加密的", + "type": "string" + }, + "status": { + "description": "群状态,0=正常 1=封禁 2=解散", + "type": "integer" + } + } + }, + "types.GetMuteListReq": { + "type": "object", + "properties": { + "id": { + "description": "群 ID", + "type": "integer" + }, + "idStr": { + "description": "如果同时填了 idStr, 则优先选择 idStr", + "type": "string" + } + } + }, + "types.GetMuteListResp": { + "type": "object", + "properties": { + "members": { + "type": "array", + "items": { + "$ref": "#/definitions/types.GroupMember" + } + } + } + }, + "types.GroupDisbandReq": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "idStr": { + "description": "如果同时填了 idStr, 则优先选择 idStr", + "type": "string" + } + } + }, + "types.GroupDisbandResp": { + "type": "object" + }, + "types.GroupExitReq": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "idStr": { + "description": "如果同时填了 idStr, 则优先选择 idStr", + "type": "string" + } + } + }, + "types.GroupExitResp": { + "type": "object" + }, + "types.GroupInfo": { + "type": "object", + "properties": { + "adminNum": { + "description": "群内管理员数量", + "type": "integer" + }, + "avatar": { + "description": "头像 url", + "type": "string" + }, + "createTime": { + "description": "群创建时间", + "type": "integer" + }, + "friendType": { + "description": "加好友限制, 0=群内可加好友,1=群内禁止加好友", + "type": "integer" + }, + "groupType": { + "description": "群类型 (0: 普通群, 1: 全员群, 2: 部门群)", + "type": "integer" + }, + "id": { + "description": "群 ID", + "type": "integer" + }, + "idStr": { + "type": "string" + }, + "introduce": { + "type": "string" + }, + "joinType": { + "description": "加群方式,0=无需审批(默认),1=禁止加群,群主和管理员邀请加群, 2=普通人邀请需要审批,群主和管理员直接加群", + "type": "integer" + }, + "key": { + "type": "string" + }, + "markId": { + "description": "群显示的 ID", + "type": "string" + }, + "maximum": { + "description": "群人数上限", + "type": "integer" + }, + "memberNum": { + "description": "群人数", + "type": "integer" + }, + "muteNum": { + "description": "群内当前被禁言的人数", + "type": "integer" + }, + "muteType": { + "description": "禁言, 0=全员可发言, 1=全员禁言(除群主和管理员)", + "type": "integer" + }, + "name": { + "description": "群名称 加密的", + "type": "string" + }, + "owner": { + "description": "群主 信息", + "$ref": "#/definitions/types.GroupMember" + }, + "person": { + "description": "本人在群内的信息", + "$ref": "#/definitions/types.GroupMember" + }, + "publicName": { + "description": "公开的群名称 不加密的", + "type": "string" + }, + "status": { + "description": "群状态,0=正常 1=封禁 2=解散", + "type": "integer" + } + } + }, + "types.GroupMember": { + "type": "object", + "properties": { + "memberId": { + "description": "用户 ID", + "type": "string" + }, + "memberMuteTime": { + "description": "该用户被禁言结束的时间 9223372036854775807=永久禁言", + "type": "integer" + }, + "memberName": { + "description": "用户群昵称", + "type": "string" + }, + "memberType": { + "description": "用户角色,2=群主,1=管理员,0=群员,10=退群", + "type": "integer" + } + } + }, + "types.GroupRemoveReq": { + "type": "object", + "required": [ + "memberIds" + ], + "properties": { + "id": { + "type": "integer" + }, + "idStr": { + "description": "如果同时填了 idStr, 则优先选择 idStr", + "type": "string" + }, + "memberIds": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "types.GroupRemoveResp": { + "type": "object", + "properties": { + "memberIds": { + "description": "成功被踢的成员列表", + "type": "array", + "items": { + "type": "string" + } + }, + "memberNum": { + "description": "群人数", + "type": "integer" + } + } + }, + "types.InviteGroupMembersReq": { + "type": "object", + "required": [ + "newMemberIds" + ], + "properties": { + "id": { + "type": "integer" + }, + "idStr": { + "description": "如果同时填了 idStr, 则优先选择 idStr", + "type": "string" + }, + "newMemberIds": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "types.InviteGroupMembersResp": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "example": 123821199217135616 + }, + "idStr": { + "type": "string" + }, + "memberNum": { + "type": "integer", + "example": 5 + } + } + }, + "types.JoinGroupReq": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "idStr": { + "type": "string" + }, + "inviterId": { + "type": "string" + } + } + }, + "types.JoinGroupResp": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "idStr": { + "type": "string" + } + } + }, + "types.SetAdminReq": { + "type": "object", + "required": [ + "memberId" + ], + "properties": { + "id": { + "description": "群 ID", + "type": "integer" + }, + "idStr": { + "description": "如果同时填了 idStr, 则优先选择 idStr", + "type": "string" + }, + "memberId": { + "description": "被设置的群成员 ID", + "type": "string" + }, + "memberType": { + "description": "用户角色 0=群员, 1=管理员", + "type": "integer" + } + } + }, + "types.SetAdminResp": { + "type": "object" + }, + "types.UpdateGroupAvatarReq": { + "type": "object", + "properties": { + "avatar": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "idStr": { + "description": "如果同时填了 idStr, 则优先选择 idStr", + "type": "string" + } + } + }, + "types.UpdateGroupAvatarResp": { + "type": "object" + }, + "types.UpdateGroupFriendTypeReq": { + "type": "object", + "properties": { + "friendType": { + "description": "加好友限制, 0=群内可加好友,1=群内禁止加好友", + "type": "integer" + }, + "id": { + "description": "群 ID", + "type": "integer" + }, + "idStr": { + "description": "如果同时填了 idStr, 则优先选择 idStr", + "type": "string" + } + } + }, + "types.UpdateGroupFriendTypeResp": { + "type": "object" + }, + "types.UpdateGroupJoinTypeReq": { + "type": "object", + "properties": { + "id": { + "description": "群 ID", + "type": "integer" + }, + "idStr": { + "description": "如果同时填了 idStr, 则优先选择 idStr", + "type": "string" + }, + "joinType": { + "description": "加群方式,0=无需审批(默认),1=禁止加群,群主和管理员邀请加群, 2=普通人邀请需要审批,群主和管理员直接加群", + "type": "integer" + } + } + }, + "types.UpdateGroupJoinTypeResp": { + "type": "object" + }, + "types.UpdateGroupMemberMuteTimeReq": { + "type": "object", + "required": [ + "memberIds" + ], + "properties": { + "id": { + "description": "群 ID", + "type": "integer" + }, + "idStr": { + "description": "如果同时填了 idStr, 则优先选择 idStr", + "type": "string" + }, + "memberIds": { + "description": "被禁言的群员 ID", + "type": "array", + "items": { + "type": "string" + } + }, + "muteTime": { + "description": "禁言持续时间, 传9223372036854775807=永久禁言, 0=解除禁言", + "type": "integer" + } + } + }, + "types.UpdateGroupMemberMuteTimeResp": { + "type": "object", + "properties": { + "members": { + "type": "array", + "items": { + "$ref": "#/definitions/types.GroupMember" + } + } + } + }, + "types.UpdateGroupMemberNameReq": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "idStr": { + "description": "如果同时填了 idStr, 则优先选择 idStr", + "type": "string" + }, + "memberName": { + "type": "string" + } + } + }, + "types.UpdateGroupMemberNameResp": { + "type": "object" + }, + "types.UpdateGroupMuteTypeReq": { + "type": "object", + "properties": { + "id": { + "description": "群 ID", + "type": "integer" + }, + "idStr": { + "description": "如果同时填了 idStr, 则优先选择 idStr", + "type": "string" + }, + "muteType": { + "description": "禁言, 0=全员可发言, 1=全员禁言(除群主和管理员)", + "type": "integer" + } + } + }, + "types.UpdateGroupMuteTypeResp": { + "type": "object" + }, + "types.UpdateGroupNameReq": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "idStr": { + "description": "如果同时填了 idStr, 则优先选择 idStr", + "type": "string" + }, + "name": { + "type": "string" + }, + "publicName": { + "type": "string" + } + } + }, + "types.UpdateGroupNameResp": { + "type": "object" + } + } +}` + +type swaggerInfo struct { + Version string + Host string + BasePath string + Schemes []string + Title string + Description string +} + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = swaggerInfo{ + Version: "1.0", + Host: "localhost:8080", + BasePath: "/api/v1", + Schemes: []string{"https"}, + Title: "即时通讯系统后端接口", + Description: "", +} + +type s struct{} + +func (s *s) ReadDoc() string { + sInfo := SwaggerInfo + sInfo.Description = strings.Replace(sInfo.Description, "\n", "\\n", -1) + + t, err := template.New("swagger_info").Funcs(template.FuncMap{ + "marshal": func(v interface{}) string { + a, _ := json.Marshal(v) + return string(a) + }, + }).Parse(doc) + if err != nil { + return doc + } + + var tpl bytes.Buffer + if err := t.Execute(&tpl, sInfo); err != nil { + return doc + } + + return tpl.String() +} + +func init() { + swag.Register(swag.Name, &s{}) +} diff --git a/gateway/api/v1/docs/swagger.json b/gateway/api/v1/docs/swagger.json new file mode 100644 index 0000000..94475f4 --- /dev/null +++ b/gateway/api/v1/docs/swagger.json @@ -0,0 +1,2270 @@ +{ + "schemes": [ + "https" + ], + "swagger": "2.0", + "info": { + "title": "即时通讯系统后端接口", + "contact": {}, + "version": "1.0" + }, + "host": "localhost:8080", + "basePath": "/api/v1", + "paths": { + "/app/group-list": { + "post": { + "tags": [ + "group 群信息" + ], + "summary": "查询群列表", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.GetGroupListReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResp" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.GetGroupListResp" + } + } + } + ] + } + } + } + } + }, + "/app/modules/all": { + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "startup 初始化模块" + ], + "summary": "获取模块启用状态", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/model.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/model.GetModuleResp" + } + } + } + } + ] + } + } + } + } + }, + "/app/mute-list": { + "post": { + "tags": [ + "group 禁言" + ], + "summary": "查询群内被禁言成员名单", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.GetMuteListReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResp" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.GetMuteListResp" + } + } + } + ] + } + } + } + } + }, + "/app/pri-chat-record": { + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "record 消息模块" + ], + "summary": "获得聊天记录", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/model.GetPriRecordsReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/model.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.GetPriRecordsResp" + } + } + } + ] + } + } + } + } + }, + "/app/record/focus": { + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "record 消息模块" + ], + "summary": "关注消息", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/model.FocusMsgReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/model.GeneralResponse" + } + } + } + } + }, + "/app/record/revoke": { + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "record 消息模块" + ], + "summary": "撤回消息", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/model.RevokeMsgReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/model.GeneralResponse" + } + } + } + } + }, + "/group/app/avatar": { + "post": { + "tags": [ + "group 群信息" + ], + "summary": "更新群头像", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.UpdateGroupAvatarReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResp" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.UpdateGroupAvatarResp" + } + } + } + ] + } + } + } + } + }, + "/group/app/change-owner": { + "post": { + "tags": [ + "group 群动作" + ], + "summary": "转让群", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.ChangeOwnerReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResp" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.ChangeOwnerResp" + } + } + } + ] + } + } + } + } + }, + "/group/app/create-group": { + "post": { + "tags": [ + "group 群动作" + ], + "summary": "创建群", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.CreateGroupReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResp" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.CreateGroupResp" + } + } + } + ] + } + } + } + } + }, + "/group/app/friendType": { + "post": { + "tags": [ + "group 群信息" + ], + "summary": "更新群内加好友设置", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.UpdateGroupFriendTypeReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResp" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.UpdateGroupFriendTypeResp" + } + } + } + ] + } + } + } + } + }, + "/group/app/group-disband": { + "post": { + "tags": [ + "group 群动作" + ], + "summary": "解散群", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.GroupDisbandReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResp" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.GroupDisbandResp" + } + } + } + ] + } + } + } + } + }, + "/group/app/group-exit": { + "post": { + "tags": [ + "group 群动作" + ], + "summary": "退群", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.GroupExitReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResp" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.GroupExitResp" + } + } + } + ] + } + } + } + } + }, + "/group/app/group-info": { + "post": { + "tags": [ + "group 群信息" + ], + "summary": "查询群信息", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.GetGroupInfoReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResp" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.GetGroupInfoResp" + } + } + } + ] + } + } + } + } + }, + "/group/app/group-member-info": { + "post": { + "tags": [ + "group 群成员信息" + ], + "summary": "查询群成员信息", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.GetGroupMemberInfoReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResp" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.GetGroupMemberInfoResp" + } + } + } + ] + } + } + } + } + }, + "/group/app/group-member-list": { + "post": { + "tags": [ + "group 群成员信息" + ], + "summary": "查询群成员列表", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.GetGroupMemberListReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResp" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.GetGroupMemberListResp" + } + } + } + ] + } + } + } + } + }, + "/group/app/group-pub-info": { + "post": { + "tags": [ + "group 群信息" + ], + "summary": "查询群公开信息", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.GetGroupPubInfoReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResp" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.GetGroupPubInfoResp" + } + } + } + ] + } + } + } + } + }, + "/group/app/group-remove": { + "post": { + "tags": [ + "group 群动作" + ], + "summary": "踢人", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.GroupRemoveReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResp" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.GroupRemoveResp" + } + } + } + ] + } + } + } + } + }, + "/group/app/invite-group-members": { + "post": { + "tags": [ + "group 群动作" + ], + "summary": "邀请新群员", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.InviteGroupMembersReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResp" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.InviteGroupMembersResp" + } + } + } + ] + } + } + } + } + }, + "/group/app/join-group": { + "post": { + "tags": [ + "group 群动作" + ], + "summary": "直接进群", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.JoinGroupReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResp" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.JoinGroupResp" + } + } + } + ] + } + } + } + } + }, + "/group/app/joinType": { + "post": { + "tags": [ + "group 群信息" + ], + "summary": "更新群内加好友设置", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.UpdateGroupJoinTypeReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResp" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.UpdateGroupJoinTypeResp" + } + } + } + ] + } + } + } + } + }, + "/group/app/member/muteTime": { + "post": { + "tags": [ + "group 禁言" + ], + "summary": "更新群成员禁言时间", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.UpdateGroupMemberMuteTimeReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResp" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.UpdateGroupMemberMuteTimeResp" + } + } + } + ] + } + } + } + } + }, + "/group/app/member/name": { + "post": { + "tags": [ + "group 群成员信息" + ], + "summary": "更新群成员昵称", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.UpdateGroupMemberNameReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResp" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.UpdateGroupMemberNameResp" + } + } + } + ] + } + } + } + } + }, + "/group/app/member/type": { + "post": { + "tags": [ + "group 群成员信息" + ], + "summary": "设置管理员", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.SetAdminReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResp" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.SetAdminResp" + } + } + } + ] + } + } + } + } + }, + "/group/app/muteType": { + "post": { + "tags": [ + "group 群信息" + ], + "summary": "更新群内加好友设置", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.UpdateGroupMuteTypeReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResp" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.UpdateGroupMuteTypeResp" + } + } + } + ] + } + } + } + } + }, + "/group/app/name": { + "post": { + "tags": [ + "group 群信息" + ], + "summary": "更新群名称", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.UpdateGroupNameReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResp" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.UpdateGroupNameResp" + } + } + } + ] + } + } + } + } + }, + "/record/push": { + "post": { + "description": "comet.Proto由接口组装,客户端只需传入comet.Proto的body部分", + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "tags": [ + "record 消息模块" + ], + "summary": "推送消息", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "消息协议序列化", + "name": "message", + "in": "body", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/model.GeneralResponse" + } + } + } + } + }, + "/record/push2": { + "post": { + "description": "comet.Proto由客户端传入", + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "tags": [ + "record 消息模块" + ], + "summary": "推送消息2", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "消息协议序列化", + "name": "message", + "in": "body", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/model.GeneralResponse" + } + } + } + } + }, + "/user/login": { + "post": { + "description": "内部接口,comet层使用", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "account 账户模块" + ], + "summary": "用户登录", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/model.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.AddressLoginResp" + } + } + } + ] + } + } + } + } + } + }, + "definitions": { + "model.AddressLoginResp": { + "type": "object", + "properties": { + "address": { + "description": "用户地址", + "type": "string", + "example": "123" + } + } + }, + "model.FocusMsgReq": { + "type": "object", + "required": [ + "logId" + ], + "properties": { + "logId": { + "type": "integer" + }, + "type": { + "type": "integer", + "enum": [ + 0, + 1 + ] + } + } + }, + "model.GeneralResponse": { + "type": "object", + "properties": { + "data": { + "type": "object" + }, + "message": { + "type": "string" + }, + "result": { + "type": "integer" + } + } + }, + "model.GetModuleResp": { + "type": "object", + "properties": { + "endPoints": { + "type": "array", + "items": { + "type": "string" + } + }, + "isEnabled": { + "type": "boolean" + }, + "name": { + "type": "string", + "enum": [ + "wallet", + "oa", + "redpacket" + ] + } + } + }, + "model.GetPriRecordsReq": { + "type": "object", + "required": [ + "count", + "targetId" + ], + "properties": { + "count": { + "description": "消息数量", + "type": "integer" + }, + "logId": { + "description": "消息 ID", + "type": "string" + }, + "targetId": { + "description": "接受者 ID", + "type": "string" + } + } + }, + "model.GetPriRecordsResp": { + "type": "object", + "properties": { + "record_count": { + "description": "聊天记录数量", + "type": "integer" + }, + "records": { + "description": "聊天记录", + "type": "array", + "items": { + "$ref": "#/definitions/model.Record" + } + } + } + }, + "model.Record": { + "type": "object", + "properties": { + "content": { + "description": "消息内容", + "type": "object" + }, + "createTime": { + "description": "消息发送时间", + "type": "integer" + }, + "fromId": { + "description": "发送者 id", + "type": "string" + }, + "logId": { + "description": "log id", + "type": "string" + }, + "msgId": { + "description": "msg id (uuid)", + "type": "string" + }, + "msgType": { + "description": "消息类型", + "type": "integer" + }, + "targetId": { + "description": "接收者 id", + "type": "string" + } + } + }, + "model.RevokeMsgReq": { + "type": "object", + "required": [ + "logId" + ], + "properties": { + "logId": { + "type": "integer" + }, + "type": { + "type": "integer", + "enum": [ + 0, + 1 + ] + } + } + }, + "types.ChangeOwnerReq": { + "type": "object", + "required": [ + "memberId" + ], + "properties": { + "id": { + "description": "群 ID", + "type": "integer" + }, + "idStr": { + "description": "如果同时填了 idStr, 则优先选择 idStr", + "type": "string" + }, + "memberId": { + "description": "被转让为群主的群成员 ID", + "type": "string" + } + } + }, + "types.ChangeOwnerResp": { + "type": "object" + }, + "types.CreateGroupReq": { + "type": "object", + "properties": { + "avatar": { + "type": "string" + }, + "introduce": { + "type": "string" + }, + "memberIds": { + "type": "array", + "items": { + "type": "string" + } + }, + "name": { + "type": "string" + } + } + }, + "types.CreateGroupResp": { + "type": "object", + "properties": { + "adminNum": { + "description": "群内管理员数量", + "type": "integer" + }, + "avatar": { + "description": "头像 url", + "type": "string" + }, + "createTime": { + "description": "群创建时间", + "type": "integer" + }, + "friendType": { + "description": "加好友限制, 0=群内可加好友,1=群内禁止加好友", + "type": "integer" + }, + "groupType": { + "description": "群类型 (0: 普通群, 1: 全员群, 2: 部门群)", + "type": "integer" + }, + "id": { + "description": "群 ID", + "type": "integer" + }, + "idStr": { + "type": "string" + }, + "introduce": { + "type": "string" + }, + "joinType": { + "description": "加群方式,0=无需审批(默认),1=禁止加群,群主和管理员邀请加群, 2=普通人邀请需要审批,群主和管理员直接加群", + "type": "integer" + }, + "key": { + "type": "string" + }, + "markId": { + "description": "群显示的 ID", + "type": "string" + }, + "maximum": { + "description": "群人数上限", + "type": "integer" + }, + "memberNum": { + "description": "群人数", + "type": "integer" + }, + "members": { + "description": "群成员", + "type": "array", + "items": { + "$ref": "#/definitions/types.GroupMember" + } + }, + "muteNum": { + "description": "群内当前被禁言的人数", + "type": "integer" + }, + "muteType": { + "description": "禁言, 0=全员可发言, 1=全员禁言(除群主和管理员)", + "type": "integer" + }, + "name": { + "description": "群名称 加密的", + "type": "string" + }, + "owner": { + "description": "群主 信息", + "$ref": "#/definitions/types.GroupMember" + }, + "person": { + "description": "本人在群内的信息", + "$ref": "#/definitions/types.GroupMember" + }, + "publicName": { + "description": "公开的群名称 不加密的", + "type": "string" + }, + "status": { + "description": "群状态,0=正常 1=封禁 2=解散", + "type": "integer" + } + } + }, + "types.GeneralResp": { + "type": "object", + "properties": { + "data": { + "type": "object" + }, + "message": { + "type": "integer" + }, + "result": { + "type": "integer" + } + } + }, + "types.GetGroupInfoReq": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "idStr": { + "description": "如果同时填了 idStr, 则优先选择 idStr", + "type": "string" + } + } + }, + "types.GetGroupInfoResp": { + "type": "object", + "properties": { + "adminNum": { + "description": "群内管理员数量", + "type": "integer" + }, + "avatar": { + "description": "头像 url", + "type": "string" + }, + "createTime": { + "description": "群创建时间", + "type": "integer" + }, + "friendType": { + "description": "加好友限制, 0=群内可加好友,1=群内禁止加好友", + "type": "integer" + }, + "groupType": { + "description": "群类型 (0: 普通群, 1: 全员群, 2: 部门群)", + "type": "integer" + }, + "id": { + "description": "群 ID", + "type": "integer" + }, + "idStr": { + "type": "string" + }, + "introduce": { + "type": "string" + }, + "joinType": { + "description": "加群方式,0=无需审批(默认),1=禁止加群,群主和管理员邀请加群, 2=普通人邀请需要审批,群主和管理员直接加群", + "type": "integer" + }, + "key": { + "type": "string" + }, + "markId": { + "description": "群显示的 ID", + "type": "string" + }, + "maximum": { + "description": "群人数上限", + "type": "integer" + }, + "memberNum": { + "description": "群人数", + "type": "integer" + }, + "members": { + "type": "array", + "items": { + "$ref": "#/definitions/types.GroupMember" + } + }, + "muteNum": { + "description": "群内当前被禁言的人数", + "type": "integer" + }, + "muteType": { + "description": "禁言, 0=全员可发言, 1=全员禁言(除群主和管理员)", + "type": "integer" + }, + "name": { + "description": "群名称 加密的", + "type": "string" + }, + "owner": { + "description": "群主 信息", + "$ref": "#/definitions/types.GroupMember" + }, + "person": { + "description": "本人在群内的信息", + "$ref": "#/definitions/types.GroupMember" + }, + "publicName": { + "description": "公开的群名称 不加密的", + "type": "string" + }, + "status": { + "description": "群状态,0=正常 1=封禁 2=解散", + "type": "integer" + } + } + }, + "types.GetGroupListReq": { + "type": "object" + }, + "types.GetGroupListResp": { + "type": "object", + "properties": { + "groups": { + "type": "array", + "items": { + "$ref": "#/definitions/types.GroupInfo" + } + } + } + }, + "types.GetGroupMemberInfoReq": { + "type": "object", + "required": [ + "memberId" + ], + "properties": { + "id": { + "type": "integer" + }, + "idStr": { + "description": "如果同时填了 idStr, 则优先选择 idStr", + "type": "string" + }, + "memberId": { + "type": "string" + } + } + }, + "types.GetGroupMemberInfoResp": { + "type": "object", + "properties": { + "memberId": { + "description": "用户 ID", + "type": "string" + }, + "memberMuteTime": { + "description": "该用户被禁言结束的时间 9223372036854775807=永久禁言", + "type": "integer" + }, + "memberName": { + "description": "用户群昵称", + "type": "string" + }, + "memberType": { + "description": "用户角色,2=群主,1=管理员,0=群员,10=退群", + "type": "integer" + } + } + }, + "types.GetGroupMemberListReq": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "idStr": { + "description": "如果同时填了 idStr, 则优先选择 idStr", + "type": "string" + } + } + }, + "types.GetGroupMemberListResp": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "idStr": { + "description": "如果同时填了 idStr, 则优先选择 idStr", + "type": "string" + }, + "members": { + "type": "array", + "items": { + "$ref": "#/definitions/types.GroupMember" + } + } + } + }, + "types.GetGroupPubInfoReq": { + "type": "object", + "properties": { + "id": { + "description": "群 ID", + "type": "integer" + }, + "idStr": { + "description": "如果同时填了 idStr, 则优先选择 idStr", + "type": "string" + } + } + }, + "types.GetGroupPubInfoResp": { + "type": "object", + "properties": { + "adminNum": { + "description": "群内管理员数量", + "type": "integer" + }, + "avatar": { + "description": "头像 url", + "type": "string" + }, + "createTime": { + "description": "群创建时间", + "type": "integer" + }, + "friendType": { + "description": "加好友限制, 0=群内可加好友,1=群内禁止加好友", + "type": "integer" + }, + "groupType": { + "description": "群类型 (0: 普通群, 1: 全员群, 2: 部门群)", + "type": "integer" + }, + "id": { + "description": "群 ID", + "type": "integer" + }, + "idStr": { + "type": "string" + }, + "introduce": { + "type": "string" + }, + "joinType": { + "description": "加群方式,0=无需审批(默认),1=禁止加群,群主和管理员邀请加群, 2=普通人邀请需要审批,群主和管理员直接加群", + "type": "integer" + }, + "key": { + "type": "string" + }, + "markId": { + "description": "群显示的 ID", + "type": "string" + }, + "maximum": { + "description": "群人数上限", + "type": "integer" + }, + "memberNum": { + "description": "群人数", + "type": "integer" + }, + "muteNum": { + "description": "群内当前被禁言的人数", + "type": "integer" + }, + "muteType": { + "description": "禁言, 0=全员可发言, 1=全员禁言(除群主和管理员)", + "type": "integer" + }, + "name": { + "description": "群名称 加密的", + "type": "string" + }, + "owner": { + "description": "群主 信息", + "$ref": "#/definitions/types.GroupMember" + }, + "person": { + "description": "本人在群内的信息", + "$ref": "#/definitions/types.GroupMember" + }, + "publicName": { + "description": "公开的群名称 不加密的", + "type": "string" + }, + "status": { + "description": "群状态,0=正常 1=封禁 2=解散", + "type": "integer" + } + } + }, + "types.GetMuteListReq": { + "type": "object", + "properties": { + "id": { + "description": "群 ID", + "type": "integer" + }, + "idStr": { + "description": "如果同时填了 idStr, 则优先选择 idStr", + "type": "string" + } + } + }, + "types.GetMuteListResp": { + "type": "object", + "properties": { + "members": { + "type": "array", + "items": { + "$ref": "#/definitions/types.GroupMember" + } + } + } + }, + "types.GroupDisbandReq": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "idStr": { + "description": "如果同时填了 idStr, 则优先选择 idStr", + "type": "string" + } + } + }, + "types.GroupDisbandResp": { + "type": "object" + }, + "types.GroupExitReq": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "idStr": { + "description": "如果同时填了 idStr, 则优先选择 idStr", + "type": "string" + } + } + }, + "types.GroupExitResp": { + "type": "object" + }, + "types.GroupInfo": { + "type": "object", + "properties": { + "adminNum": { + "description": "群内管理员数量", + "type": "integer" + }, + "avatar": { + "description": "头像 url", + "type": "string" + }, + "createTime": { + "description": "群创建时间", + "type": "integer" + }, + "friendType": { + "description": "加好友限制, 0=群内可加好友,1=群内禁止加好友", + "type": "integer" + }, + "groupType": { + "description": "群类型 (0: 普通群, 1: 全员群, 2: 部门群)", + "type": "integer" + }, + "id": { + "description": "群 ID", + "type": "integer" + }, + "idStr": { + "type": "string" + }, + "introduce": { + "type": "string" + }, + "joinType": { + "description": "加群方式,0=无需审批(默认),1=禁止加群,群主和管理员邀请加群, 2=普通人邀请需要审批,群主和管理员直接加群", + "type": "integer" + }, + "key": { + "type": "string" + }, + "markId": { + "description": "群显示的 ID", + "type": "string" + }, + "maximum": { + "description": "群人数上限", + "type": "integer" + }, + "memberNum": { + "description": "群人数", + "type": "integer" + }, + "muteNum": { + "description": "群内当前被禁言的人数", + "type": "integer" + }, + "muteType": { + "description": "禁言, 0=全员可发言, 1=全员禁言(除群主和管理员)", + "type": "integer" + }, + "name": { + "description": "群名称 加密的", + "type": "string" + }, + "owner": { + "description": "群主 信息", + "$ref": "#/definitions/types.GroupMember" + }, + "person": { + "description": "本人在群内的信息", + "$ref": "#/definitions/types.GroupMember" + }, + "publicName": { + "description": "公开的群名称 不加密的", + "type": "string" + }, + "status": { + "description": "群状态,0=正常 1=封禁 2=解散", + "type": "integer" + } + } + }, + "types.GroupMember": { + "type": "object", + "properties": { + "memberId": { + "description": "用户 ID", + "type": "string" + }, + "memberMuteTime": { + "description": "该用户被禁言结束的时间 9223372036854775807=永久禁言", + "type": "integer" + }, + "memberName": { + "description": "用户群昵称", + "type": "string" + }, + "memberType": { + "description": "用户角色,2=群主,1=管理员,0=群员,10=退群", + "type": "integer" + } + } + }, + "types.GroupRemoveReq": { + "type": "object", + "required": [ + "memberIds" + ], + "properties": { + "id": { + "type": "integer" + }, + "idStr": { + "description": "如果同时填了 idStr, 则优先选择 idStr", + "type": "string" + }, + "memberIds": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "types.GroupRemoveResp": { + "type": "object", + "properties": { + "memberIds": { + "description": "成功被踢的成员列表", + "type": "array", + "items": { + "type": "string" + } + }, + "memberNum": { + "description": "群人数", + "type": "integer" + } + } + }, + "types.InviteGroupMembersReq": { + "type": "object", + "required": [ + "newMemberIds" + ], + "properties": { + "id": { + "type": "integer" + }, + "idStr": { + "description": "如果同时填了 idStr, 则优先选择 idStr", + "type": "string" + }, + "newMemberIds": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "types.InviteGroupMembersResp": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "example": 123821199217135616 + }, + "idStr": { + "type": "string" + }, + "memberNum": { + "type": "integer", + "example": 5 + } + } + }, + "types.JoinGroupReq": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "idStr": { + "type": "string" + }, + "inviterId": { + "type": "string" + } + } + }, + "types.JoinGroupResp": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "idStr": { + "type": "string" + } + } + }, + "types.SetAdminReq": { + "type": "object", + "required": [ + "memberId" + ], + "properties": { + "id": { + "description": "群 ID", + "type": "integer" + }, + "idStr": { + "description": "如果同时填了 idStr, 则优先选择 idStr", + "type": "string" + }, + "memberId": { + "description": "被设置的群成员 ID", + "type": "string" + }, + "memberType": { + "description": "用户角色 0=群员, 1=管理员", + "type": "integer" + } + } + }, + "types.SetAdminResp": { + "type": "object" + }, + "types.UpdateGroupAvatarReq": { + "type": "object", + "properties": { + "avatar": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "idStr": { + "description": "如果同时填了 idStr, 则优先选择 idStr", + "type": "string" + } + } + }, + "types.UpdateGroupAvatarResp": { + "type": "object" + }, + "types.UpdateGroupFriendTypeReq": { + "type": "object", + "properties": { + "friendType": { + "description": "加好友限制, 0=群内可加好友,1=群内禁止加好友", + "type": "integer" + }, + "id": { + "description": "群 ID", + "type": "integer" + }, + "idStr": { + "description": "如果同时填了 idStr, 则优先选择 idStr", + "type": "string" + } + } + }, + "types.UpdateGroupFriendTypeResp": { + "type": "object" + }, + "types.UpdateGroupJoinTypeReq": { + "type": "object", + "properties": { + "id": { + "description": "群 ID", + "type": "integer" + }, + "idStr": { + "description": "如果同时填了 idStr, 则优先选择 idStr", + "type": "string" + }, + "joinType": { + "description": "加群方式,0=无需审批(默认),1=禁止加群,群主和管理员邀请加群, 2=普通人邀请需要审批,群主和管理员直接加群", + "type": "integer" + } + } + }, + "types.UpdateGroupJoinTypeResp": { + "type": "object" + }, + "types.UpdateGroupMemberMuteTimeReq": { + "type": "object", + "required": [ + "memberIds" + ], + "properties": { + "id": { + "description": "群 ID", + "type": "integer" + }, + "idStr": { + "description": "如果同时填了 idStr, 则优先选择 idStr", + "type": "string" + }, + "memberIds": { + "description": "被禁言的群员 ID", + "type": "array", + "items": { + "type": "string" + } + }, + "muteTime": { + "description": "禁言持续时间, 传9223372036854775807=永久禁言, 0=解除禁言", + "type": "integer" + } + } + }, + "types.UpdateGroupMemberMuteTimeResp": { + "type": "object", + "properties": { + "members": { + "type": "array", + "items": { + "$ref": "#/definitions/types.GroupMember" + } + } + } + }, + "types.UpdateGroupMemberNameReq": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "idStr": { + "description": "如果同时填了 idStr, 则优先选择 idStr", + "type": "string" + }, + "memberName": { + "type": "string" + } + } + }, + "types.UpdateGroupMemberNameResp": { + "type": "object" + }, + "types.UpdateGroupMuteTypeReq": { + "type": "object", + "properties": { + "id": { + "description": "群 ID", + "type": "integer" + }, + "idStr": { + "description": "如果同时填了 idStr, 则优先选择 idStr", + "type": "string" + }, + "muteType": { + "description": "禁言, 0=全员可发言, 1=全员禁言(除群主和管理员)", + "type": "integer" + } + } + }, + "types.UpdateGroupMuteTypeResp": { + "type": "object" + }, + "types.UpdateGroupNameReq": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "idStr": { + "description": "如果同时填了 idStr, 则优先选择 idStr", + "type": "string" + }, + "name": { + "type": "string" + }, + "publicName": { + "type": "string" + } + } + }, + "types.UpdateGroupNameResp": { + "type": "object" + } + } +} \ No newline at end of file diff --git a/gateway/api/v1/docs/swagger.yaml b/gateway/api/v1/docs/swagger.yaml new file mode 100644 index 0000000..7963be0 --- /dev/null +++ b/gateway/api/v1/docs/swagger.yaml @@ -0,0 +1,1447 @@ +basePath: /api/v1 +definitions: + model.AddressLoginResp: + properties: + address: + description: 用户地址 + example: "123" + type: string + type: object + model.FocusMsgReq: + properties: + logId: + type: integer + type: + enum: + - 0 + - 1 + type: integer + required: + - logId + type: object + model.GeneralResponse: + properties: + data: + type: object + message: + type: string + result: + type: integer + type: object + model.GetModuleResp: + properties: + endPoints: + items: + type: string + type: array + isEnabled: + type: boolean + name: + enum: + - wallet + - oa + - redpacket + type: string + type: object + model.GetPriRecordsReq: + properties: + count: + description: 消息数量 + type: integer + logId: + description: 消息 ID + type: string + targetId: + description: 接受者 ID + type: string + required: + - count + - targetId + type: object + model.GetPriRecordsResp: + properties: + record_count: + description: 聊天记录数量 + type: integer + records: + description: 聊天记录 + items: + $ref: '#/definitions/model.Record' + type: array + type: object + model.Record: + properties: + content: + description: 消息内容 + type: object + createTime: + description: 消息发送时间 + type: integer + fromId: + description: 发送者 id + type: string + logId: + description: log id + type: string + msgId: + description: msg id (uuid) + type: string + msgType: + description: 消息类型 + type: integer + targetId: + description: 接收者 id + type: string + type: object + model.RevokeMsgReq: + properties: + logId: + type: integer + type: + enum: + - 0 + - 1 + type: integer + required: + - logId + type: object + types.ChangeOwnerReq: + properties: + id: + description: 群 ID + type: integer + idStr: + description: 如果同时填了 idStr, 则优先选择 idStr + type: string + memberId: + description: 被转让为群主的群成员 ID + type: string + required: + - memberId + type: object + types.ChangeOwnerResp: + type: object + types.CreateGroupReq: + properties: + avatar: + type: string + introduce: + type: string + memberIds: + items: + type: string + type: array + name: + type: string + type: object + types.CreateGroupResp: + properties: + adminNum: + description: 群内管理员数量 + type: integer + avatar: + description: 头像 url + type: string + createTime: + description: 群创建时间 + type: integer + friendType: + description: 加好友限制, 0=群内可加好友,1=群内禁止加好友 + type: integer + groupType: + description: '群类型 (0: 普通群, 1: 全员群, 2: 部门群)' + type: integer + id: + description: 群 ID + type: integer + idStr: + type: string + introduce: + type: string + joinType: + description: 加群方式,0=无需审批(默认),1=禁止加群,群主和管理员邀请加群, 2=普通人邀请需要审批,群主和管理员直接加群 + type: integer + key: + type: string + markId: + description: 群显示的 ID + type: string + maximum: + description: 群人数上限 + type: integer + memberNum: + description: 群人数 + type: integer + members: + description: 群成员 + items: + $ref: '#/definitions/types.GroupMember' + type: array + muteNum: + description: 群内当前被禁言的人数 + type: integer + muteType: + description: 禁言, 0=全员可发言, 1=全员禁言(除群主和管理员) + type: integer + name: + description: 群名称 加密的 + type: string + owner: + $ref: '#/definitions/types.GroupMember' + description: 群主 信息 + person: + $ref: '#/definitions/types.GroupMember' + description: 本人在群内的信息 + publicName: + description: 公开的群名称 不加密的 + type: string + status: + description: 群状态,0=正常 1=封禁 2=解散 + type: integer + type: object + types.GeneralResp: + properties: + data: + type: object + message: + type: integer + result: + type: integer + type: object + types.GetGroupInfoReq: + properties: + id: + type: integer + idStr: + description: 如果同时填了 idStr, 则优先选择 idStr + type: string + type: object + types.GetGroupInfoResp: + properties: + adminNum: + description: 群内管理员数量 + type: integer + avatar: + description: 头像 url + type: string + createTime: + description: 群创建时间 + type: integer + friendType: + description: 加好友限制, 0=群内可加好友,1=群内禁止加好友 + type: integer + groupType: + description: '群类型 (0: 普通群, 1: 全员群, 2: 部门群)' + type: integer + id: + description: 群 ID + type: integer + idStr: + type: string + introduce: + type: string + joinType: + description: 加群方式,0=无需审批(默认),1=禁止加群,群主和管理员邀请加群, 2=普通人邀请需要审批,群主和管理员直接加群 + type: integer + key: + type: string + markId: + description: 群显示的 ID + type: string + maximum: + description: 群人数上限 + type: integer + memberNum: + description: 群人数 + type: integer + members: + items: + $ref: '#/definitions/types.GroupMember' + type: array + muteNum: + description: 群内当前被禁言的人数 + type: integer + muteType: + description: 禁言, 0=全员可发言, 1=全员禁言(除群主和管理员) + type: integer + name: + description: 群名称 加密的 + type: string + owner: + $ref: '#/definitions/types.GroupMember' + description: 群主 信息 + person: + $ref: '#/definitions/types.GroupMember' + description: 本人在群内的信息 + publicName: + description: 公开的群名称 不加密的 + type: string + status: + description: 群状态,0=正常 1=封禁 2=解散 + type: integer + type: object + types.GetGroupListReq: + type: object + types.GetGroupListResp: + properties: + groups: + items: + $ref: '#/definitions/types.GroupInfo' + type: array + type: object + types.GetGroupMemberInfoReq: + properties: + id: + type: integer + idStr: + description: 如果同时填了 idStr, 则优先选择 idStr + type: string + memberId: + type: string + required: + - memberId + type: object + types.GetGroupMemberInfoResp: + properties: + memberId: + description: 用户 ID + type: string + memberMuteTime: + description: 该用户被禁言结束的时间 9223372036854775807=永久禁言 + type: integer + memberName: + description: 用户群昵称 + type: string + memberType: + description: 用户角色,2=群主,1=管理员,0=群员,10=退群 + type: integer + type: object + types.GetGroupMemberListReq: + properties: + id: + type: integer + idStr: + description: 如果同时填了 idStr, 则优先选择 idStr + type: string + type: object + types.GetGroupMemberListResp: + properties: + id: + type: integer + idStr: + description: 如果同时填了 idStr, 则优先选择 idStr + type: string + members: + items: + $ref: '#/definitions/types.GroupMember' + type: array + type: object + types.GetGroupPubInfoReq: + properties: + id: + description: 群 ID + type: integer + idStr: + description: 如果同时填了 idStr, 则优先选择 idStr + type: string + type: object + types.GetGroupPubInfoResp: + properties: + adminNum: + description: 群内管理员数量 + type: integer + avatar: + description: 头像 url + type: string + createTime: + description: 群创建时间 + type: integer + friendType: + description: 加好友限制, 0=群内可加好友,1=群内禁止加好友 + type: integer + groupType: + description: '群类型 (0: 普通群, 1: 全员群, 2: 部门群)' + type: integer + id: + description: 群 ID + type: integer + idStr: + type: string + introduce: + type: string + joinType: + description: 加群方式,0=无需审批(默认),1=禁止加群,群主和管理员邀请加群, 2=普通人邀请需要审批,群主和管理员直接加群 + type: integer + key: + type: string + markId: + description: 群显示的 ID + type: string + maximum: + description: 群人数上限 + type: integer + memberNum: + description: 群人数 + type: integer + muteNum: + description: 群内当前被禁言的人数 + type: integer + muteType: + description: 禁言, 0=全员可发言, 1=全员禁言(除群主和管理员) + type: integer + name: + description: 群名称 加密的 + type: string + owner: + $ref: '#/definitions/types.GroupMember' + description: 群主 信息 + person: + $ref: '#/definitions/types.GroupMember' + description: 本人在群内的信息 + publicName: + description: 公开的群名称 不加密的 + type: string + status: + description: 群状态,0=正常 1=封禁 2=解散 + type: integer + type: object + types.GetMuteListReq: + properties: + id: + description: 群 ID + type: integer + idStr: + description: 如果同时填了 idStr, 则优先选择 idStr + type: string + type: object + types.GetMuteListResp: + properties: + members: + items: + $ref: '#/definitions/types.GroupMember' + type: array + type: object + types.GroupDisbandReq: + properties: + id: + type: integer + idStr: + description: 如果同时填了 idStr, 则优先选择 idStr + type: string + type: object + types.GroupDisbandResp: + type: object + types.GroupExitReq: + properties: + id: + type: integer + idStr: + description: 如果同时填了 idStr, 则优先选择 idStr + type: string + type: object + types.GroupExitResp: + type: object + types.GroupInfo: + properties: + adminNum: + description: 群内管理员数量 + type: integer + avatar: + description: 头像 url + type: string + createTime: + description: 群创建时间 + type: integer + friendType: + description: 加好友限制, 0=群内可加好友,1=群内禁止加好友 + type: integer + groupType: + description: '群类型 (0: 普通群, 1: 全员群, 2: 部门群)' + type: integer + id: + description: 群 ID + type: integer + idStr: + type: string + introduce: + type: string + joinType: + description: 加群方式,0=无需审批(默认),1=禁止加群,群主和管理员邀请加群, 2=普通人邀请需要审批,群主和管理员直接加群 + type: integer + key: + type: string + markId: + description: 群显示的 ID + type: string + maximum: + description: 群人数上限 + type: integer + memberNum: + description: 群人数 + type: integer + muteNum: + description: 群内当前被禁言的人数 + type: integer + muteType: + description: 禁言, 0=全员可发言, 1=全员禁言(除群主和管理员) + type: integer + name: + description: 群名称 加密的 + type: string + owner: + $ref: '#/definitions/types.GroupMember' + description: 群主 信息 + person: + $ref: '#/definitions/types.GroupMember' + description: 本人在群内的信息 + publicName: + description: 公开的群名称 不加密的 + type: string + status: + description: 群状态,0=正常 1=封禁 2=解散 + type: integer + type: object + types.GroupMember: + properties: + memberId: + description: 用户 ID + type: string + memberMuteTime: + description: 该用户被禁言结束的时间 9223372036854775807=永久禁言 + type: integer + memberName: + description: 用户群昵称 + type: string + memberType: + description: 用户角色,2=群主,1=管理员,0=群员,10=退群 + type: integer + type: object + types.GroupRemoveReq: + properties: + id: + type: integer + idStr: + description: 如果同时填了 idStr, 则优先选择 idStr + type: string + memberIds: + items: + type: string + type: array + required: + - memberIds + type: object + types.GroupRemoveResp: + properties: + memberIds: + description: 成功被踢的成员列表 + items: + type: string + type: array + memberNum: + description: 群人数 + type: integer + type: object + types.InviteGroupMembersReq: + properties: + id: + type: integer + idStr: + description: 如果同时填了 idStr, 则优先选择 idStr + type: string + newMemberIds: + items: + type: string + type: array + required: + - newMemberIds + type: object + types.InviteGroupMembersResp: + properties: + id: + example: 123821199217135616 + type: integer + idStr: + type: string + memberNum: + example: 5 + type: integer + type: object + types.JoinGroupReq: + properties: + id: + type: integer + idStr: + type: string + inviterId: + type: string + type: object + types.JoinGroupResp: + properties: + id: + type: integer + idStr: + type: string + type: object + types.SetAdminReq: + properties: + id: + description: 群 ID + type: integer + idStr: + description: 如果同时填了 idStr, 则优先选择 idStr + type: string + memberId: + description: 被设置的群成员 ID + type: string + memberType: + description: 用户角色 0=群员, 1=管理员 + type: integer + required: + - memberId + type: object + types.SetAdminResp: + type: object + types.UpdateGroupAvatarReq: + properties: + avatar: + type: string + id: + type: integer + idStr: + description: 如果同时填了 idStr, 则优先选择 idStr + type: string + type: object + types.UpdateGroupAvatarResp: + type: object + types.UpdateGroupFriendTypeReq: + properties: + friendType: + description: 加好友限制, 0=群内可加好友,1=群内禁止加好友 + type: integer + id: + description: 群 ID + type: integer + idStr: + description: 如果同时填了 idStr, 则优先选择 idStr + type: string + type: object + types.UpdateGroupFriendTypeResp: + type: object + types.UpdateGroupJoinTypeReq: + properties: + id: + description: 群 ID + type: integer + idStr: + description: 如果同时填了 idStr, 则优先选择 idStr + type: string + joinType: + description: 加群方式,0=无需审批(默认),1=禁止加群,群主和管理员邀请加群, 2=普通人邀请需要审批,群主和管理员直接加群 + type: integer + type: object + types.UpdateGroupJoinTypeResp: + type: object + types.UpdateGroupMemberMuteTimeReq: + properties: + id: + description: 群 ID + type: integer + idStr: + description: 如果同时填了 idStr, 则优先选择 idStr + type: string + memberIds: + description: 被禁言的群员 ID + items: + type: string + type: array + muteTime: + description: 禁言持续时间, 传9223372036854775807=永久禁言, 0=解除禁言 + type: integer + required: + - memberIds + type: object + types.UpdateGroupMemberMuteTimeResp: + properties: + members: + items: + $ref: '#/definitions/types.GroupMember' + type: array + type: object + types.UpdateGroupMemberNameReq: + properties: + id: + type: integer + idStr: + description: 如果同时填了 idStr, 则优先选择 idStr + type: string + memberName: + type: string + type: object + types.UpdateGroupMemberNameResp: + type: object + types.UpdateGroupMuteTypeReq: + properties: + id: + description: 群 ID + type: integer + idStr: + description: 如果同时填了 idStr, 则优先选择 idStr + type: string + muteType: + description: 禁言, 0=全员可发言, 1=全员禁言(除群主和管理员) + type: integer + type: object + types.UpdateGroupMuteTypeResp: + type: object + types.UpdateGroupNameReq: + properties: + id: + type: integer + idStr: + description: 如果同时填了 idStr, 则优先选择 idStr + type: string + name: + type: string + publicName: + type: string + type: object + types.UpdateGroupNameResp: + type: object +host: localhost:8080 +info: + contact: {} + title: 即时通讯系统后端接口 + version: "1.0" +paths: + /app/group-list: + post: + parameters: + - description: MOCK + in: header + name: FZM-SIGNATURE + required: true + type: string + - description: body + in: body + name: data + schema: + $ref: '#/definitions/types.GetGroupListReq' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/types.GeneralResp' + - properties: + data: + $ref: '#/definitions/types.GetGroupListResp' + type: object + summary: 查询群列表 + tags: + - group 群信息 + /app/modules/all: + post: + consumes: + - application/json + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/model.GeneralResponse' + - properties: + data: + items: + $ref: '#/definitions/model.GetModuleResp' + type: array + type: object + summary: 获取模块启用状态 + tags: + - startup 初始化模块 + /app/mute-list: + post: + parameters: + - description: MOCK + in: header + name: FZM-SIGNATURE + required: true + type: string + - description: body + in: body + name: data + schema: + $ref: '#/definitions/types.GetMuteListReq' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/types.GeneralResp' + - properties: + data: + $ref: '#/definitions/types.GetMuteListResp' + type: object + summary: 查询群内被禁言成员名单 + tags: + - group 禁言 + /app/pri-chat-record: + post: + consumes: + - application/json + parameters: + - description: MOCK + in: header + name: FZM-SIGNATURE + required: true + type: string + - description: body + in: body + name: data + schema: + $ref: '#/definitions/model.GetPriRecordsReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/model.GeneralResponse' + - properties: + data: + $ref: '#/definitions/model.GetPriRecordsResp' + type: object + summary: 获得聊天记录 + tags: + - record 消息模块 + /app/record/focus: + post: + consumes: + - application/json + parameters: + - description: MOCK + in: header + name: FZM-SIGNATURE + required: true + type: string + - description: body + in: body + name: data + required: true + schema: + $ref: '#/definitions/model.FocusMsgReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/model.GeneralResponse' + summary: 关注消息 + tags: + - record 消息模块 + /app/record/revoke: + post: + consumes: + - application/json + parameters: + - description: MOCK + in: header + name: FZM-SIGNATURE + required: true + type: string + - description: body + in: body + name: data + required: true + schema: + $ref: '#/definitions/model.RevokeMsgReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/model.GeneralResponse' + summary: 撤回消息 + tags: + - record 消息模块 + /group/app/avatar: + post: + parameters: + - description: MOCK + in: header + name: FZM-SIGNATURE + required: true + type: string + - description: body + in: body + name: data + schema: + $ref: '#/definitions/types.UpdateGroupAvatarReq' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/types.GeneralResp' + - properties: + data: + $ref: '#/definitions/types.UpdateGroupAvatarResp' + type: object + summary: 更新群头像 + tags: + - group 群信息 + /group/app/change-owner: + post: + parameters: + - description: MOCK + in: header + name: FZM-SIGNATURE + required: true + type: string + - description: body + in: body + name: data + schema: + $ref: '#/definitions/types.ChangeOwnerReq' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/types.GeneralResp' + - properties: + data: + $ref: '#/definitions/types.ChangeOwnerResp' + type: object + summary: 转让群 + tags: + - group 群动作 + /group/app/create-group: + post: + parameters: + - description: MOCK + in: header + name: FZM-SIGNATURE + required: true + type: string + - description: body + in: body + name: data + schema: + $ref: '#/definitions/types.CreateGroupReq' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/types.GeneralResp' + - properties: + data: + $ref: '#/definitions/types.CreateGroupResp' + type: object + summary: 创建群 + tags: + - group 群动作 + /group/app/friendType: + post: + parameters: + - description: MOCK + in: header + name: FZM-SIGNATURE + required: true + type: string + - description: body + in: body + name: data + schema: + $ref: '#/definitions/types.UpdateGroupFriendTypeReq' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/types.GeneralResp' + - properties: + data: + $ref: '#/definitions/types.UpdateGroupFriendTypeResp' + type: object + summary: 更新群内加好友设置 + tags: + - group 群信息 + /group/app/group-disband: + post: + parameters: + - description: MOCK + in: header + name: FZM-SIGNATURE + required: true + type: string + - description: body + in: body + name: data + schema: + $ref: '#/definitions/types.GroupDisbandReq' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/types.GeneralResp' + - properties: + data: + $ref: '#/definitions/types.GroupDisbandResp' + type: object + summary: 解散群 + tags: + - group 群动作 + /group/app/group-exit: + post: + parameters: + - description: MOCK + in: header + name: FZM-SIGNATURE + required: true + type: string + - description: body + in: body + name: data + schema: + $ref: '#/definitions/types.GroupExitReq' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/types.GeneralResp' + - properties: + data: + $ref: '#/definitions/types.GroupExitResp' + type: object + summary: 退群 + tags: + - group 群动作 + /group/app/group-info: + post: + parameters: + - description: MOCK + in: header + name: FZM-SIGNATURE + required: true + type: string + - description: body + in: body + name: data + schema: + $ref: '#/definitions/types.GetGroupInfoReq' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/types.GeneralResp' + - properties: + data: + $ref: '#/definitions/types.GetGroupInfoResp' + type: object + summary: 查询群信息 + tags: + - group 群信息 + /group/app/group-member-info: + post: + parameters: + - description: MOCK + in: header + name: FZM-SIGNATURE + required: true + type: string + - description: body + in: body + name: data + schema: + $ref: '#/definitions/types.GetGroupMemberInfoReq' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/types.GeneralResp' + - properties: + data: + $ref: '#/definitions/types.GetGroupMemberInfoResp' + type: object + summary: 查询群成员信息 + tags: + - group 群成员信息 + /group/app/group-member-list: + post: + parameters: + - description: MOCK + in: header + name: FZM-SIGNATURE + required: true + type: string + - description: body + in: body + name: data + schema: + $ref: '#/definitions/types.GetGroupMemberListReq' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/types.GeneralResp' + - properties: + data: + $ref: '#/definitions/types.GetGroupMemberListResp' + type: object + summary: 查询群成员列表 + tags: + - group 群成员信息 + /group/app/group-pub-info: + post: + parameters: + - description: MOCK + in: header + name: FZM-SIGNATURE + required: true + type: string + - description: body + in: body + name: data + schema: + $ref: '#/definitions/types.GetGroupPubInfoReq' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/types.GeneralResp' + - properties: + data: + $ref: '#/definitions/types.GetGroupPubInfoResp' + type: object + summary: 查询群公开信息 + tags: + - group 群信息 + /group/app/group-remove: + post: + parameters: + - description: MOCK + in: header + name: FZM-SIGNATURE + required: true + type: string + - description: body + in: body + name: data + schema: + $ref: '#/definitions/types.GroupRemoveReq' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/types.GeneralResp' + - properties: + data: + $ref: '#/definitions/types.GroupRemoveResp' + type: object + summary: 踢人 + tags: + - group 群动作 + /group/app/invite-group-members: + post: + parameters: + - description: MOCK + in: header + name: FZM-SIGNATURE + required: true + type: string + - description: body + in: body + name: data + schema: + $ref: '#/definitions/types.InviteGroupMembersReq' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/types.GeneralResp' + - properties: + data: + $ref: '#/definitions/types.InviteGroupMembersResp' + type: object + summary: 邀请新群员 + tags: + - group 群动作 + /group/app/join-group: + post: + parameters: + - description: MOCK + in: header + name: FZM-SIGNATURE + required: true + type: string + - description: body + in: body + name: data + schema: + $ref: '#/definitions/types.JoinGroupReq' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/types.GeneralResp' + - properties: + data: + $ref: '#/definitions/types.JoinGroupResp' + type: object + summary: 直接进群 + tags: + - group 群动作 + /group/app/joinType: + post: + parameters: + - description: MOCK + in: header + name: FZM-SIGNATURE + required: true + type: string + - description: body + in: body + name: data + schema: + $ref: '#/definitions/types.UpdateGroupJoinTypeReq' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/types.GeneralResp' + - properties: + data: + $ref: '#/definitions/types.UpdateGroupJoinTypeResp' + type: object + summary: 更新群内加好友设置 + tags: + - group 群信息 + /group/app/member/muteTime: + post: + parameters: + - description: MOCK + in: header + name: FZM-SIGNATURE + required: true + type: string + - description: body + in: body + name: data + schema: + $ref: '#/definitions/types.UpdateGroupMemberMuteTimeReq' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/types.GeneralResp' + - properties: + data: + $ref: '#/definitions/types.UpdateGroupMemberMuteTimeResp' + type: object + summary: 更新群成员禁言时间 + tags: + - group 禁言 + /group/app/member/name: + post: + parameters: + - description: MOCK + in: header + name: FZM-SIGNATURE + required: true + type: string + - description: body + in: body + name: data + schema: + $ref: '#/definitions/types.UpdateGroupMemberNameReq' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/types.GeneralResp' + - properties: + data: + $ref: '#/definitions/types.UpdateGroupMemberNameResp' + type: object + summary: 更新群成员昵称 + tags: + - group 群成员信息 + /group/app/member/type: + post: + parameters: + - description: MOCK + in: header + name: FZM-SIGNATURE + required: true + type: string + - description: body + in: body + name: data + schema: + $ref: '#/definitions/types.SetAdminReq' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/types.GeneralResp' + - properties: + data: + $ref: '#/definitions/types.SetAdminResp' + type: object + summary: 设置管理员 + tags: + - group 群成员信息 + /group/app/muteType: + post: + parameters: + - description: MOCK + in: header + name: FZM-SIGNATURE + required: true + type: string + - description: body + in: body + name: data + schema: + $ref: '#/definitions/types.UpdateGroupMuteTypeReq' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/types.GeneralResp' + - properties: + data: + $ref: '#/definitions/types.UpdateGroupMuteTypeResp' + type: object + summary: 更新群内加好友设置 + tags: + - group 群信息 + /group/app/name: + post: + parameters: + - description: MOCK + in: header + name: FZM-SIGNATURE + required: true + type: string + - description: body + in: body + name: data + schema: + $ref: '#/definitions/types.UpdateGroupNameReq' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/types.GeneralResp' + - properties: + data: + $ref: '#/definitions/types.UpdateGroupNameResp' + type: object + summary: 更新群名称 + tags: + - group 群信息 + /record/push: + post: + consumes: + - multipart/form-data + description: comet.Proto由接口组装,客户端只需传入comet.Proto的body部分 + parameters: + - description: MOCK + in: header + name: FZM-SIGNATURE + required: true + type: string + - description: 消息协议序列化 + in: body + name: message + required: true + schema: + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/model.GeneralResponse' + summary: 推送消息 + tags: + - record 消息模块 + /record/push2: + post: + consumes: + - multipart/form-data + description: comet.Proto由客户端传入 + parameters: + - description: MOCK + in: header + name: FZM-SIGNATURE + required: true + type: string + - description: 消息协议序列化 + in: body + name: message + required: true + schema: + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/model.GeneralResponse' + summary: 推送消息2 + tags: + - record 消息模块 + /user/login: + post: + consumes: + - application/json + description: 内部接口,comet层使用 + parameters: + - description: MOCK + in: header + name: FZM-SIGNATURE + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/model.GeneralResponse' + - properties: + data: + $ref: '#/definitions/model.AddressLoginResp' + type: object + summary: 用户登录 + tags: + - account 账户模块 +schemes: +- https +swagger: "2.0" diff --git a/gateway/api/v1/etc/gateway.toml b/gateway/api/v1/etc/gateway.toml new file mode 100644 index 0000000..f062463 --- /dev/null +++ b/gateway/api/v1/etc/gateway.toml @@ -0,0 +1,63 @@ +env = "debug" + +[server] +addr = "0.0.0.0:19000" + +[Trace] +ServiceName = "gateway" +Gen128Bit = true +[Trace.Sampler] +Type = "const" +Param = 1.0 +[Trace.Reporter] +LogSpans = true +LocalAgentHostPort = "127.0.0.1:6831" + +[AnswerRPCClient] +RegAddrs = "127.0.0.1:2379" +Schema = "dtalk" +SrvName = "answer" +Dial = "1s" +Timeout = "1s" + +[StoreRPCClient] +RegAddrs = "127.0.0.1:2379" +Schema = "dtalk" +SrvName = "store" +Dial = "1s" +Timeout = "1s" + +[GroupRPCClient] +RegAddrs = "127.0.0.1:2379" +Schema = "dtalk" +SrvName = "group" +Dial = "1s" +Timeout = "1s" + +[[modules]] +Name = "wallet" +IsEnabled = true +EndPoints = ["http://172.16.101.87:8901","http://172.16.101.107:8083"] # changeme (1. 红包服务 http 服务地址, 2. 钱包服务 http 服务地址) + +[[modules]] +Name = "redPacket" +IsEnabled = true +EndPoints = ["http://172.16.101.87:8901","http://172.16.101.107:8083"] # changeme (1. 红包服务 http 服务地址, 2. 钱包服务 http 服务地址) + +[[modules]] +Name = "oa" +IsEnabled = true +EndPoints = ["http://127.0.0.1:20000"] # changeme (oa 服务地址) + +[[modules]] +Name = "shop" +IsEnabled = true +EndPoints = ["http://146.56.218.121:12009"] # changeme (链上购服务地址) + +[[modules]] +Name="live" +IsEnabled=true +EndPoints=[""] + +[Revoke] +Expire = "86400h" #ten years (撤回消息有效时间) \ No newline at end of file diff --git a/gateway/api/v1/gateway.go b/gateway/api/v1/gateway.go new file mode 100644 index 0000000..ef3450b --- /dev/null +++ b/gateway/api/v1/gateway.go @@ -0,0 +1,106 @@ +package main + +import ( + "context" + "flag" + "fmt" + "os" + "os/signal" + "syscall" + "time" + + "github.com/opentracing/opentracing-go" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/config" + http "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/handler" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/svc" + "gitlab.33.cn/chat/im-pkg/trace" +) + +const srvName = "gateway" + +var ( + // projectVersion 项目版本 + projectVersion = "0.1.2" + // goVersion go版本 + goVersion = "" + // gitCommit git提交commit id + gitCommit = "" + // buildTime 编译时间 + buildTime = "" + + isShowVersion = flag.Bool("v", false, "show project version") +) + +// showVersion 显示项目版本信息 +func showVersion(isShow bool) { + if isShow { + fmt.Printf("Project: %s\n", srvName) + fmt.Printf(" Version: %s\n", projectVersion) + fmt.Printf(" Go Version: %s\n", goVersion) + fmt.Printf(" Git Commit: %s\n", gitCommit) + fmt.Printf(" Build Time: %s\n", buildTime) + os.Exit(0) + } +} + +// @Title 聊天网关 +// @Version 0.1 +// @Description +// @SecurityDefinitions.ApiKey ApiKeyAuth +// @In header +// @Name Authorization +// @BasePath / +func main() { + flag.Parse() + showVersion(*isShowVersion) + + if err := config.Init(); err != nil { + panic(err) + } + + //log init + log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stdout}).With().Str("service", srvName).Logger() + zerolog.SetGlobalLevel(zerolog.InfoLevel) + + log.Info(). + Interface("Modules", config.Conf.Modules). + Interface("Server", config.Conf.Server). + Msg("config info") + + //trace init + tracer, tracerCloser := trace.Init(srvName, config.Conf.Trace) + //不然后续不会有Jaeger实例 + opentracing.SetGlobalTracer(tracer) + + // service init + ctx := svc.NewServiceContext(*config.Conf) + httpSrv := http.Init(ctx) + + // init signal + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT) + for { + s := <-c + log.Info().Str("signal", s.String()).Msg("service get a signal") + switch s { + case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT: + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := httpSrv.Shutdown(ctx); err != nil { + log.Error().Err(err).Msg("server shutdown") + } + if err := tracerCloser.Close(); err != nil { + log.Error().Err(err).Msg("tracer close failed") + } + time.Sleep(time.Second * 2) + log.Info().Str("name", srvName).Msg("server exit") + return + case syscall.SIGHUP: + // TODO reload + default: + return + } + } +} diff --git a/gateway/api/v1/internal/config/config.go b/gateway/api/v1/internal/config/config.go new file mode 100644 index 0000000..d6c25c1 --- /dev/null +++ b/gateway/api/v1/internal/config/config.go @@ -0,0 +1,106 @@ +package config + +import ( + "flag" + "github.com/BurntSushi/toml" + "github.com/uber/jaeger-client-go" + traceConfig "github.com/uber/jaeger-client-go/config" + xtime "gitlab.33.cn/chat/dtalk/pkg/time" + "time" +) + +var ( + confPath string + + Conf *Config +) + +func init() { + flag.StringVar(&confPath, "conf", "gateway.toml", "default config path.") +} + +// Init init config. +func Init() (err error) { + Conf = Default() + _, err = toml.DecodeFile(confPath, &Conf) + return +} + +func Default() *Config { + return &Config{ + Env: "debug", + Server: &HttpServer{ + Addr: "0.0.0.0:18002", + }, + Trace: traceConfig.Configuration{ + ServiceName: "gateway", + Gen128Bit: true, + Sampler: &traceConfig.SamplerConfig{ + Type: jaeger.SamplerTypeConst, + Param: 1, + }, + Reporter: &traceConfig.ReporterConfig{ + LogSpans: true, + LocalAgentHostPort: "127.0.0.1:6831", + }, + }, + Revoke: &Revoke{ + Expire: xtime.Duration(time.Hour * 24), + }, + AnswerRPCClient: &RPCClient{ + RegAddrs: "127.0.0.1:2379", + Schema: "dtalk", + SrvName: "answer", + Dial: xtime.Duration(time.Second), + Timeout: xtime.Duration(time.Second), + }, + StoreRPCClient: &RPCClient{ + RegAddrs: "127.0.0.1:2379", + Schema: "dtalk", + SrvName: "store", + Dial: xtime.Duration(time.Second), + Timeout: xtime.Duration(time.Second), + }, + GroupRPCClient: &RPCClient{ + RegAddrs: "127.0.0.1:2379", + Schema: "dtalk", + SrvName: "group", + Dial: xtime.Duration(time.Second), + Timeout: xtime.Duration(time.Second), + }, + } +} + +type Config struct { + Env string + Server *HttpServer + Trace traceConfig.Configuration + Modules []Module + Revoke *Revoke + AnswerRPCClient *RPCClient + StoreRPCClient *RPCClient + GroupRPCClient *RPCClient +} + +type HttpServer struct { + Addr string +} + +// RPCClient is RPC client config. +type RPCClient struct { + RegAddrs string // etcd addrs, seperate by ',' + Schema string + SrvName string // call + Dial xtime.Duration + Timeout xtime.Duration +} + +type Module struct { + Name string `json:"name"` // enums: wallet、oa、redpacket + IsEnabled bool `json:"isEnabled"` + EndPoints []string `json:"endPoints"` +} + +type Revoke struct { + Expire xtime.Duration +} diff --git a/gateway/api/v1/internal/handler/account/userloginhandler.go b/gateway/api/v1/internal/handler/account/userloginhandler.go new file mode 100644 index 0000000..173608f --- /dev/null +++ b/gateway/api/v1/internal/handler/account/userloginhandler.go @@ -0,0 +1,27 @@ +package account + +import ( + "github.com/gin-gonic/gin" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/model" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/svc" + "gitlab.33.cn/chat/dtalk/pkg/api" +) + +// AddressLogin +// @Summary 用户登录 +// @Description 内部接口,comet层使用 +// @Author dld@33.cn +// @Tags account 账户模块 +// @Accept json +// @Produce json +// @Param FZM-SIGNATURE header string true "MOCK" +// @Success 200 {object} model.GeneralResponse{data=model.AddressLoginResp} +// @Router /user/login [post] +func AddressLogin(ctx *svc.ServiceContext) gin.HandlerFunc { + return func(c *gin.Context) { + c.Set(api.ReqResult, &model.AddressLoginResp{ + Address: c.MustGet(api.Address).(string), + }) + c.Set(api.ReqError, nil) + } +} diff --git a/gateway/api/v1/internal/handler/group/change_owner.go b/gateway/api/v1/internal/handler/group/change_owner.go new file mode 100644 index 0000000..fe171d8 --- /dev/null +++ b/gateway/api/v1/internal/handler/group/change_owner.go @@ -0,0 +1,45 @@ +package group + +import ( + "github.com/gin-gonic/gin" + logic "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/logic/group" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/svc" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/types" + "gitlab.33.cn/chat/dtalk/pkg/api" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/utils/go-kit/convert" +) + +// ChangeOwnerHandler +// @Summary 转让群 +// @Author chy@33.cn +// @Tags group 群动作 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.ChangeOwnerReq false "body" +// @Success 200 {object} types.GeneralResp{data=types.ChangeOwnerResp} +// @Router /group/app/change-owner [post] +func ChangeOwnerHandler(ctx *svc.ServiceContext) gin.HandlerFunc { + return func(c *gin.Context) { + req := &types.ChangeOwnerReq{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + if req.Id == 0 && req.IdStr == "" { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.IdStr != "" { + req.Id = convert.ToInt64(req.IdStr) + } + + l := logic.NewGroupLogic(c, ctx) + res, err := l.ChangeOwner(req) + + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) + } +} diff --git a/gateway/api/v1/internal/handler/group/create_group.go b/gateway/api/v1/internal/handler/group/create_group.go new file mode 100644 index 0000000..794e3ec --- /dev/null +++ b/gateway/api/v1/internal/handler/group/create_group.go @@ -0,0 +1,35 @@ +package group + +import ( + "github.com/gin-gonic/gin" + logic "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/logic/group" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/svc" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/types" + "gitlab.33.cn/chat/dtalk/pkg/api" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" +) + +// CreateGroupHandler +// @Summary 创建群 +// @Author chy@33.cn +// @Tags group 群动作 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.CreateGroupReq false "body" +// @Success 200 {object} types.GeneralResp{data=types.CreateGroupResp} +// @Router /group/app/create-group [post] +func CreateGroupHandler(ctx *svc.ServiceContext) gin.HandlerFunc { + return func(c *gin.Context) { + req := &types.CreateGroupReq{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + l := logic.NewGroupLogic(c, ctx) + res, err := l.CreateGroup(req) + + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) + } +} diff --git a/gateway/api/v1/internal/handler/group/get_group_info.go b/gateway/api/v1/internal/handler/group/get_group_info.go new file mode 100644 index 0000000..f6cfbe6 --- /dev/null +++ b/gateway/api/v1/internal/handler/group/get_group_info.go @@ -0,0 +1,45 @@ +package group + +import ( + "github.com/gin-gonic/gin" + logic "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/logic/group" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/svc" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/types" + "gitlab.33.cn/chat/dtalk/pkg/api" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/utils/go-kit/convert" +) + +// GetGroupInfoHandler +// @Summary 查询群信息 +// @Author chy@33.cn +// @Tags group 群信息 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.GetGroupInfoReq false "body" +// @Success 200 {object} types.GeneralResp{data=types.GetGroupInfoResp} +// @Router /group/app/group-info [post] +func GetGroupInfoHandler(ctx *svc.ServiceContext) gin.HandlerFunc { + return func(c *gin.Context) { + req := &types.GetGroupInfoReq{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + if req.Id == 0 && req.IdStr == "" { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.IdStr != "" { + req.Id = convert.ToInt64(req.IdStr) + } + + l := logic.NewGroupLogic(c, ctx) + res, err := l.GetPriGroupInfo(req) + + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) + } +} diff --git a/gateway/api/v1/internal/handler/group/get_group_list.go b/gateway/api/v1/internal/handler/group/get_group_list.go new file mode 100644 index 0000000..10ae58f --- /dev/null +++ b/gateway/api/v1/internal/handler/group/get_group_list.go @@ -0,0 +1,29 @@ +package group + +import ( + "github.com/gin-gonic/gin" + logic "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/logic/group" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/svc" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/types" + "gitlab.33.cn/chat/dtalk/pkg/api" +) + +// GetGroupListHandler +// @Summary 查询群列表 +// @Author chy@33.cn +// @Tags group 群信息 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.GetGroupListReq false "body" +// @Success 200 {object} types.GeneralResp{data=types.GetGroupListResp} +// @Router /app/group-list [post] +func GetGroupListHandler(ctx *svc.ServiceContext) gin.HandlerFunc { + return func(c *gin.Context) { + req := &types.GetGroupListReq{} + + l := logic.NewGroupLogic(c, ctx) + res, err := l.GetGroupList(req) + + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) + } +} diff --git a/gateway/api/v1/internal/handler/group/get_group_member_info.go b/gateway/api/v1/internal/handler/group/get_group_member_info.go new file mode 100644 index 0000000..7ed9609 --- /dev/null +++ b/gateway/api/v1/internal/handler/group/get_group_member_info.go @@ -0,0 +1,45 @@ +package group + +import ( + "github.com/gin-gonic/gin" + logic "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/logic/group" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/svc" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/types" + "gitlab.33.cn/chat/dtalk/pkg/api" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/utils/go-kit/convert" +) + +// GetGroupMemberInfoHandler +// @Summary 查询群成员信息 +// @Author chy@33.cn +// @Tags group 群成员信息 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.GetGroupMemberInfoReq false "body" +// @Success 200 {object} types.GeneralResp{data=types.GetGroupMemberInfoResp} +// @Router /group/app/group-member-info [post] +func GetGroupMemberInfoHandler(ctx *svc.ServiceContext) gin.HandlerFunc { + return func(c *gin.Context) { + req := &types.GetGroupMemberInfoReq{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + if req.Id == 0 && req.IdStr == "" { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.IdStr != "" { + req.Id = convert.ToInt64(req.IdStr) + } + + l := logic.NewGroupLogic(c, ctx) + res, err := l.GetGroupMemberInfo(req) + + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) + } +} diff --git a/gateway/api/v1/internal/handler/group/get_group_member_list.go b/gateway/api/v1/internal/handler/group/get_group_member_list.go new file mode 100644 index 0000000..dd59c89 --- /dev/null +++ b/gateway/api/v1/internal/handler/group/get_group_member_list.go @@ -0,0 +1,45 @@ +package group + +import ( + "github.com/gin-gonic/gin" + logic "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/logic/group" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/svc" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/types" + "gitlab.33.cn/chat/dtalk/pkg/api" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/utils/go-kit/convert" +) + +// GetGroupMemberListHandler +// @Summary 查询群成员列表 +// @Author chy@33.cn +// @Tags group 群成员信息 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.GetGroupMemberListReq false "body" +// @Success 200 {object} types.GeneralResp{data=types.GetGroupMemberListResp} +// @Router /group/app/group-member-list [post] +func GetGroupMemberListHandler(ctx *svc.ServiceContext) gin.HandlerFunc { + return func(c *gin.Context) { + req := &types.GetGroupMemberListReq{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + if req.Id == 0 && req.IdStr == "" { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.IdStr != "" { + req.Id = convert.ToInt64(req.IdStr) + } + + l := logic.NewGroupLogic(c, ctx) + res, err := l.GetGroupMemberList(req) + + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) + } +} diff --git a/gateway/api/v1/internal/handler/group/get_mute_list.go b/gateway/api/v1/internal/handler/group/get_mute_list.go new file mode 100644 index 0000000..0e7d5ea --- /dev/null +++ b/gateway/api/v1/internal/handler/group/get_mute_list.go @@ -0,0 +1,45 @@ +package group + +import ( + "github.com/gin-gonic/gin" + logic "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/logic/group" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/svc" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/types" + "gitlab.33.cn/chat/dtalk/pkg/api" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/utils/go-kit/convert" +) + +// GetMuteListHandler +// @Summary 查询群内被禁言成员名单 +// @Author chy@33.cn +// @Tags group 禁言 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.GetMuteListReq false "body" +// @Success 200 {object} types.GeneralResp{data=types.GetMuteListResp} +// @Router /app/mute-list [post] +func GetMuteListHandler(ctx *svc.ServiceContext) gin.HandlerFunc { + return func(c *gin.Context) { + req := &types.GetMuteListReq{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + if req.Id == 0 && req.IdStr == "" { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.IdStr != "" { + req.Id = convert.ToInt64(req.IdStr) + } + + l := logic.NewGroupLogic(c, ctx) + res, err := l.GetMuteList(req) + + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) + } +} diff --git a/gateway/api/v1/internal/handler/group/get_pub_group_info.go b/gateway/api/v1/internal/handler/group/get_pub_group_info.go new file mode 100644 index 0000000..a7b55a4 --- /dev/null +++ b/gateway/api/v1/internal/handler/group/get_pub_group_info.go @@ -0,0 +1,45 @@ +package group + +import ( + "github.com/gin-gonic/gin" + logic "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/logic/group" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/svc" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/types" + "gitlab.33.cn/chat/dtalk/pkg/api" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/utils/go-kit/convert" +) + +// GetGroupPubInfoHandler +// @Summary 查询群公开信息 +// @Author chy@33.cn +// @Tags group 群信息 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.GetGroupPubInfoReq false "body" +// @Success 200 {object} types.GeneralResp{data=types.GetGroupPubInfoResp} +// @Router /group/app/group-pub-info [post] +func GetGroupPubInfoHandler(ctx *svc.ServiceContext) gin.HandlerFunc { + return func(c *gin.Context) { + req := &types.GetGroupPubInfoReq{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + if req.Id == 0 && req.IdStr == "" { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.IdStr != "" { + req.Id = convert.ToInt64(req.IdStr) + } + + l := logic.NewGroupLogic(c, ctx) + res, err := l.GetPubGroupInfo(req) + + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) + } +} diff --git a/gateway/api/v1/internal/handler/group/group_disband.go b/gateway/api/v1/internal/handler/group/group_disband.go new file mode 100644 index 0000000..90a1dd7 --- /dev/null +++ b/gateway/api/v1/internal/handler/group/group_disband.go @@ -0,0 +1,45 @@ +package group + +import ( + "github.com/gin-gonic/gin" + logic "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/logic/group" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/svc" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/types" + "gitlab.33.cn/chat/dtalk/pkg/api" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/utils/go-kit/convert" +) + +// GroupDisbandHandler +// @Summary 解散群 +// @Author chy@33.cn +// @Tags group 群动作 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.GroupDisbandReq false "body" +// @Success 200 {object} types.GeneralResp{data=types.GroupDisbandResp} +// @Router /group/app/group-disband [post] +func GroupDisbandHandler(ctx *svc.ServiceContext) gin.HandlerFunc { + return func(c *gin.Context) { + req := &types.GroupDisbandReq{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.Id == 0 && req.IdStr == "" { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.IdStr != "" { + req.Id = convert.ToInt64(req.IdStr) + } + + l := logic.NewGroupLogic(c, ctx) + res, err := l.GroupDisband(req) + + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) + } +} diff --git a/gateway/api/v1/internal/handler/group/group_exit.go b/gateway/api/v1/internal/handler/group/group_exit.go new file mode 100644 index 0000000..7fff96e --- /dev/null +++ b/gateway/api/v1/internal/handler/group/group_exit.go @@ -0,0 +1,45 @@ +package group + +import ( + "github.com/gin-gonic/gin" + logic "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/logic/group" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/svc" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/types" + "gitlab.33.cn/chat/dtalk/pkg/api" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/utils/go-kit/convert" +) + +// GroupExitHandler +// @Summary 退群 +// @Author chy@33.cn +// @Tags group 群动作 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.GroupExitReq false "body" +// @Success 200 {object} types.GeneralResp{data=types.GroupExitResp} +// @Router /group/app/group-exit [post] +func GroupExitHandler(ctx *svc.ServiceContext) gin.HandlerFunc { + return func(c *gin.Context) { + req := &types.GroupExitReq{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.Id == 0 && req.IdStr == "" { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.IdStr != "" { + req.Id = convert.ToInt64(req.IdStr) + } + + l := logic.NewGroupLogic(c, ctx) + res, err := l.GroupExit(req) + + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) + } +} diff --git a/gateway/api/v1/internal/handler/group/group_remove.go b/gateway/api/v1/internal/handler/group/group_remove.go new file mode 100644 index 0000000..0009b82 --- /dev/null +++ b/gateway/api/v1/internal/handler/group/group_remove.go @@ -0,0 +1,45 @@ +package group + +import ( + "github.com/gin-gonic/gin" + logic "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/logic/group" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/svc" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/types" + "gitlab.33.cn/chat/dtalk/pkg/api" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/utils/go-kit/convert" +) + +// GroupRemoveHandler +// @Summary 踢人 +// @Author chy@33.cn +// @Tags group 群动作 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.GroupRemoveReq false "body" +// @Success 200 {object} types.GeneralResp{data=types.GroupRemoveResp} +// @Router /group/app/group-remove [post] +func GroupRemoveHandler(ctx *svc.ServiceContext) gin.HandlerFunc { + return func(c *gin.Context) { + req := &types.GroupRemoveReq{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.Id == 0 && req.IdStr == "" { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.IdStr != "" { + req.Id = convert.ToInt64(req.IdStr) + } + + l := logic.NewGroupLogic(c, ctx) + res, err := l.GroupRemove(req) + + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) + } +} diff --git a/gateway/api/v1/internal/handler/group/invite_group_members.go b/gateway/api/v1/internal/handler/group/invite_group_members.go new file mode 100644 index 0000000..da72eb8 --- /dev/null +++ b/gateway/api/v1/internal/handler/group/invite_group_members.go @@ -0,0 +1,45 @@ +package group + +import ( + "github.com/gin-gonic/gin" + logic "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/logic/group" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/svc" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/types" + "gitlab.33.cn/chat/dtalk/pkg/api" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/utils/go-kit/convert" +) + +// InviteGroupMembersHandler +// @Summary 邀请新群员 +// @Author chy@33.cn +// @Tags group 群动作 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.InviteGroupMembersReq false "body" +// @Success 200 {object} types.GeneralResp{data=types.InviteGroupMembersResp} +// @Router /group/app/invite-group-members [post] +func InviteGroupMembersHandler(ctx *svc.ServiceContext) gin.HandlerFunc { + return func(c *gin.Context) { + req := &types.InviteGroupMembersReq{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + if req.Id == 0 && req.IdStr == "" { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.IdStr != "" { + req.Id = convert.ToInt64(req.IdStr) + } + + l := logic.NewGroupLogic(c, ctx) + res, err := l.InviteGroupMembers(req) + + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) + } +} diff --git a/gateway/api/v1/internal/handler/group/join_group.go b/gateway/api/v1/internal/handler/group/join_group.go new file mode 100644 index 0000000..58970d9 --- /dev/null +++ b/gateway/api/v1/internal/handler/group/join_group.go @@ -0,0 +1,45 @@ +package group + +import ( + "github.com/gin-gonic/gin" + logic "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/logic/group" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/svc" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/types" + "gitlab.33.cn/chat/dtalk/pkg/api" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/utils/go-kit/convert" +) + +// JoinGroupHandler +// @Summary 直接进群 +// @Author chy@33.cn +// @Tags group 群动作 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.JoinGroupReq false "body" +// @Success 200 {object} types.GeneralResp{data=types.JoinGroupResp} +// @Router /group/app/join-group [post] +func JoinGroupHandler(ctx *svc.ServiceContext) gin.HandlerFunc { + return func(c *gin.Context) { + req := &types.JoinGroupReq{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + if req.Id == 0 && req.IdStr == "" { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.IdStr != "" { + req.Id = convert.ToInt64(req.IdStr) + } + + l := logic.NewGroupLogic(c, ctx) + res, err := l.JoinGroup(req) + + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) + } +} diff --git a/gateway/api/v1/internal/handler/group/set_admin.go b/gateway/api/v1/internal/handler/group/set_admin.go new file mode 100644 index 0000000..ac62acb --- /dev/null +++ b/gateway/api/v1/internal/handler/group/set_admin.go @@ -0,0 +1,45 @@ +package group + +import ( + "github.com/gin-gonic/gin" + logic "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/logic/group" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/svc" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/types" + "gitlab.33.cn/chat/dtalk/pkg/api" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/utils/go-kit/convert" +) + +// SetAdminHandler +// @Summary 设置管理员 +// @Author chy@33.cn +// @Tags group 群成员信息 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.SetAdminReq false "body" +// @Success 200 {object} types.GeneralResp{data=types.SetAdminResp} +// @Router /group/app/member/type [post] +func SetAdminHandler(ctx *svc.ServiceContext) gin.HandlerFunc { + return func(c *gin.Context) { + req := &types.SetAdminReq{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + if req.Id == 0 && req.IdStr == "" { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.IdStr != "" { + req.Id = convert.ToInt64(req.IdStr) + } + + l := logic.NewGroupLogic(c, ctx) + res, err := l.SetAdmin(req) + + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) + } +} diff --git a/gateway/api/v1/internal/handler/group/update_group_avatar.go b/gateway/api/v1/internal/handler/group/update_group_avatar.go new file mode 100644 index 0000000..8a6cfaf --- /dev/null +++ b/gateway/api/v1/internal/handler/group/update_group_avatar.go @@ -0,0 +1,45 @@ +package group + +import ( + "github.com/gin-gonic/gin" + logic "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/logic/group" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/svc" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/types" + "gitlab.33.cn/chat/dtalk/pkg/api" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/utils/go-kit/convert" +) + +// UpdateGroupAvatarHandler +// @Summary 更新群头像 +// @Author chy@33.cn +// @Tags group 群信息 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.UpdateGroupAvatarReq false "body" +// @Success 200 {object} types.GeneralResp{data=types.UpdateGroupAvatarResp} +// @Router /group/app/avatar [post] +func UpdateGroupAvatarHandler(ctx *svc.ServiceContext) gin.HandlerFunc { + return func(c *gin.Context) { + req := &types.UpdateGroupAvatarReq{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + if req.Id == 0 && req.IdStr == "" { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.IdStr != "" { + req.Id = convert.ToInt64(req.IdStr) + } + + l := logic.NewGroupLogic(c, ctx) + res, err := l.UpdateGroupAvatar(req) + + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) + } +} diff --git a/gateway/api/v1/internal/handler/group/update_group_friend_type.go b/gateway/api/v1/internal/handler/group/update_group_friend_type.go new file mode 100644 index 0000000..b72ff23 --- /dev/null +++ b/gateway/api/v1/internal/handler/group/update_group_friend_type.go @@ -0,0 +1,45 @@ +package group + +import ( + "github.com/gin-gonic/gin" + logic "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/logic/group" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/svc" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/types" + "gitlab.33.cn/chat/dtalk/pkg/api" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/utils/go-kit/convert" +) + +// UpdateGroupFriendTypeHandler +// @Summary 更新群内加好友设置 +// @Author chy@33.cn +// @Tags group 群信息 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.UpdateGroupFriendTypeReq false "body" +// @Success 200 {object} types.GeneralResp{data=types.UpdateGroupFriendTypeResp} +// @Router /group/app/friendType [post] +func UpdateGroupFriendTypeHandler(ctx *svc.ServiceContext) gin.HandlerFunc { + return func(c *gin.Context) { + req := &types.UpdateGroupFriendTypeReq{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + if req.Id == 0 && req.IdStr == "" { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.IdStr != "" { + req.Id = convert.ToInt64(req.IdStr) + } + + l := logic.NewGroupLogic(c, ctx) + res, err := l.UpdateGroupFriendType(req) + + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) + } +} diff --git a/gateway/api/v1/internal/handler/group/update_group_join_type.go b/gateway/api/v1/internal/handler/group/update_group_join_type.go new file mode 100644 index 0000000..842e192 --- /dev/null +++ b/gateway/api/v1/internal/handler/group/update_group_join_type.go @@ -0,0 +1,45 @@ +package group + +import ( + "github.com/gin-gonic/gin" + logic "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/logic/group" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/svc" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/types" + "gitlab.33.cn/chat/dtalk/pkg/api" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/utils/go-kit/convert" +) + +// UpdateGroupJoinTypeHandler +// @Summary 更新群内加好友设置 +// @Author chy@33.cn +// @Tags group 群信息 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.UpdateGroupJoinTypeReq false "body" +// @Success 200 {object} types.GeneralResp{data=types.UpdateGroupJoinTypeResp} +// @Router /group/app/joinType [post] +func UpdateGroupJoinTypeHandler(ctx *svc.ServiceContext) gin.HandlerFunc { + return func(c *gin.Context) { + req := &types.UpdateGroupJoinTypeReq{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + if req.Id == 0 && req.IdStr == "" { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.IdStr != "" { + req.Id = convert.ToInt64(req.IdStr) + } + + l := logic.NewGroupLogic(c, ctx) + res, err := l.UpdateGroupJoinType(req) + + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) + } +} diff --git a/gateway/api/v1/internal/handler/group/update_group_member_mute_time.go b/gateway/api/v1/internal/handler/group/update_group_member_mute_time.go new file mode 100644 index 0000000..f9b7f35 --- /dev/null +++ b/gateway/api/v1/internal/handler/group/update_group_member_mute_time.go @@ -0,0 +1,45 @@ +package group + +import ( + "github.com/gin-gonic/gin" + logic "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/logic/group" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/svc" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/types" + "gitlab.33.cn/chat/dtalk/pkg/api" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/utils/go-kit/convert" +) + +// UpdateGroupMemberMuteTimeHandler +// @Summary 更新群成员禁言时间 +// @Author chy@33.cn +// @Tags group 禁言 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.UpdateGroupMemberMuteTimeReq false "body" +// @Success 200 {object} types.GeneralResp{data=types.UpdateGroupMemberMuteTimeResp} +// @Router /group/app/member/muteTime [post] +func UpdateGroupMemberMuteTimeHandler(ctx *svc.ServiceContext) gin.HandlerFunc { + return func(c *gin.Context) { + req := &types.UpdateGroupMemberMuteTimeReq{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + if req.Id == 0 && req.IdStr == "" { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.IdStr != "" { + req.Id = convert.ToInt64(req.IdStr) + } + + l := logic.NewGroupLogic(c, ctx) + res, err := l.UpdateGroupMemberMuteTime(req) + + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) + } +} diff --git a/gateway/api/v1/internal/handler/group/update_group_member_name.go b/gateway/api/v1/internal/handler/group/update_group_member_name.go new file mode 100644 index 0000000..a8adb41 --- /dev/null +++ b/gateway/api/v1/internal/handler/group/update_group_member_name.go @@ -0,0 +1,45 @@ +package group + +import ( + "github.com/gin-gonic/gin" + logic "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/logic/group" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/svc" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/types" + "gitlab.33.cn/chat/dtalk/pkg/api" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/utils/go-kit/convert" +) + +// UpdateGroupMemberNameHandler +// @Summary 更新群成员昵称 +// @Author chy@33.cn +// @Tags group 群成员信息 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.UpdateGroupMemberNameReq false "body" +// @Success 200 {object} types.GeneralResp{data=types.UpdateGroupMemberNameResp} +// @Router /group/app/member/name [post] +func UpdateGroupMemberNameHandler(ctx *svc.ServiceContext) gin.HandlerFunc { + return func(c *gin.Context) { + req := &types.UpdateGroupMemberNameReq{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + if req.Id == 0 && req.IdStr == "" { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.IdStr != "" { + req.Id = convert.ToInt64(req.IdStr) + } + + l := logic.NewGroupLogic(c, ctx) + res, err := l.UpdateGroupMemberName(req) + + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) + } +} diff --git a/gateway/api/v1/internal/handler/group/update_group_mute_type.go b/gateway/api/v1/internal/handler/group/update_group_mute_type.go new file mode 100644 index 0000000..41bf2b8 --- /dev/null +++ b/gateway/api/v1/internal/handler/group/update_group_mute_type.go @@ -0,0 +1,45 @@ +package group + +import ( + "github.com/gin-gonic/gin" + logic "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/logic/group" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/svc" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/types" + "gitlab.33.cn/chat/dtalk/pkg/api" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/utils/go-kit/convert" +) + +// UpdateGroupMuteTypeHandler +// @Summary 更新群内加好友设置 +// @Author chy@33.cn +// @Tags group 群信息 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.UpdateGroupMuteTypeReq false "body" +// @Success 200 {object} types.GeneralResp{data=types.UpdateGroupMuteTypeResp} +// @Router /group/app/muteType [post] +func UpdateGroupMuteTypeHandler(ctx *svc.ServiceContext) gin.HandlerFunc { + return func(c *gin.Context) { + req := &types.UpdateGroupMuteTypeReq{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + if req.Id == 0 && req.IdStr == "" { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.IdStr != "" { + req.Id = convert.ToInt64(req.IdStr) + } + + l := logic.NewGroupLogic(c, ctx) + res, err := l.UpdateGroupMuteType(req) + + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) + } +} diff --git a/gateway/api/v1/internal/handler/group/update_group_name.go b/gateway/api/v1/internal/handler/group/update_group_name.go new file mode 100644 index 0000000..58fdb87 --- /dev/null +++ b/gateway/api/v1/internal/handler/group/update_group_name.go @@ -0,0 +1,45 @@ +package group + +import ( + "github.com/gin-gonic/gin" + logic "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/logic/group" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/svc" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/types" + "gitlab.33.cn/chat/dtalk/pkg/api" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/utils/go-kit/convert" +) + +// UpdateGroupNameHandler +// @Summary 更新群名称 +// @Author chy@33.cn +// @Tags group 群信息 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.UpdateGroupNameReq false "body" +// @Success 200 {object} types.GeneralResp{data=types.UpdateGroupNameResp} +// @Router /group/app/name [post] +func UpdateGroupNameHandler(ctx *svc.ServiceContext) gin.HandlerFunc { + return func(c *gin.Context) { + req := &types.UpdateGroupNameReq{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + if req.Id == 0 && req.IdStr == "" { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.IdStr != "" { + req.Id = convert.ToInt64(req.IdStr) + } + + l := logic.NewGroupLogic(c, ctx) + res, err := l.UpdateGroupName(req) + + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) + } +} diff --git a/gateway/api/v1/internal/handler/modules/getmoduleshandler.go b/gateway/api/v1/internal/handler/modules/getmoduleshandler.go new file mode 100644 index 0000000..b41c5b0 --- /dev/null +++ b/gateway/api/v1/internal/handler/modules/getmoduleshandler.go @@ -0,0 +1,34 @@ +package modules + +import ( + "github.com/gin-gonic/gin" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/model" + "gitlab.33.cn/chat/dtalk/pkg/api" + + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/svc" +) + +// GetModulesHandler +// @Summary 获取模块启用状态 +// @Description +// @Author dld@33.cn +// @Tags startup 初始化模块 +// @Accept json +// @Produce json +// @Success 200 {object} model.GeneralResponse{data=[]model.GetModuleResp} +// @Router /app/modules/all [post] +func GetModulesHandler(ctx *svc.ServiceContext) gin.HandlerFunc { + return func(c *gin.Context) { + modules := ctx.Config().Modules + var ret = make([]model.GetModuleResp, len(modules)) + for i, v := range modules { + ret[i] = model.GetModuleResp{ + Name: v.Name, + IsEnabled: v.IsEnabled, + EndPoints: v.EndPoints, + } + } + c.Set(api.ReqResult, ret) + c.Set(api.ReqError, nil) + } +} diff --git a/gateway/api/v1/internal/handler/record/focushandler.go b/gateway/api/v1/internal/handler/record/focushandler.go new file mode 100644 index 0000000..8c4219b --- /dev/null +++ b/gateway/api/v1/internal/handler/record/focushandler.go @@ -0,0 +1,48 @@ +package record + +import ( + "github.com/gin-gonic/gin" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/logic/record" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/model" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/svc" + "gitlab.33.cn/chat/dtalk/pkg/api" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" +) + +//@Summary 关注消息 +//@Description +//@Author dld@33.cn +//@Tags record 消息模块 +//@Accept json +//@Produce json +//@Param FZM-SIGNATURE header string true "MOCK" +//@Param data body model.FocusMsgReq true "body" +//@Success 200 {object} model.GeneralResponse{} +//@Router /app/record/focus [post] +func FocusHandler(ctx *svc.ServiceContext) gin.HandlerFunc { + return func(c *gin.Context) { + req := &model.FocusMsgReq{} + if err := c.ShouldBind(req); err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + var err error + operator := c.MustGet(api.Address).(string) + l := record.NewLogic(c.Request.Context(), ctx) + switch req.Type { + case model.Private: + err = l.FocusPersonal(operator, req.LogId) + case model.Group: + err = l.FocusGroup(operator, req.LogId) + default: + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage("undefined type")) + return + } + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.CodeInnerError).SetExtMessage(err.Error())) + return + } + c.Set(api.ReqResult, nil) + c.Set(api.ReqError, err) + } +} diff --git a/gateway/api/v1/internal/handler/record/pushhandler.go b/gateway/api/v1/internal/handler/record/pushhandler.go new file mode 100644 index 0000000..6cf074a --- /dev/null +++ b/gateway/api/v1/internal/handler/record/pushhandler.go @@ -0,0 +1,142 @@ +package record + +import ( + "github.com/gin-gonic/gin" + "github.com/golang/protobuf/proto" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/logic/record" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/svc" + "gitlab.33.cn/chat/dtalk/pkg/api" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + comet "gitlab.33.cn/chat/im/api/comet/grpc" + "io/ioutil" +) + +// PushToUid +// @Summary 推送消息 +// @Description comet.Proto由接口组装,客户端只需传入comet.Proto的body部分 +// @Author dld@33.cn +// @Tags record 消息模块 +// @Accept mpfd +// @Produce json +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param message body string true "消息协议序列化" +// @Success 200 {object} model.GeneralResponse{} +// @Router /record/push [post] +func PushToUid(ctx *svc.ServiceContext) gin.HandlerFunc { + return func(c *gin.Context) { + form, err := c.MultipartForm() + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage("MultipartForm"+err.Error())) + return + } + files := form.File["message"] + + if len(files) < 1 { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage("file len less than 1")) + return + } + + //file, err := c.FormFile("") + file := files[0] + f, err := file.Open() + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage("Open File"+err.Error())) + return + } + defer f.Close() + + body, err := ioutil.ReadAll(f) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage("ReadAll"+err.Error())) + return + } + if len(body) == 0 { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage("error message length 0")) + return + } + p := comet.Proto{ + Ver: 1, + Op: int32(comet.Op_SendMsg), + Seq: 0, + Ack: 0, + Body: body, + } + data, err := proto.Marshal(&p) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.SendMsgFailed).SetExtMessage(err.Error())) + return + } + uid := c.MustGet(api.Address).(string) + l := record.NewLogic(c.Request.Context(), ctx) + mid, createTime, err := l.Push("", uid, data) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.SendMsgFailed).SetExtMessage(err.Error())) + return + } + ret := map[string]interface{}{ + "logId": mid, + "datetime": createTime, + } + c.Set(api.ReqResult, ret) + c.Set(api.ReqError, nil) + } +} + +// PushToUid2 +// @Summary 推送消息2 +// @Description comet.Proto由客户端传入 +// @Author dld@33.cn +// @Tags record 消息模块 +// @Accept mpfd +// @Produce json +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param message body string true "消息协议序列化" +// @Success 200 {object} model.GeneralResponse{} +// @Router /record/push2 [post] +func PushToUid2(ctx *svc.ServiceContext) gin.HandlerFunc { + return func(c *gin.Context) { + form, err := c.MultipartForm() + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage("MultipartForm"+err.Error())) + return + } + files := form.File["message"] + + if len(files) < 1 { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage("file len less than 1")) + return + } + + //file, err := c.FormFile("") + file := files[0] + f, err := file.Open() + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage("Open File"+err.Error())) + return + } + defer f.Close() + + body, err := ioutil.ReadAll(f) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage("ReadAll"+err.Error())) + return + } + if len(body) == 0 { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage("error message length 0")) + return + } + uid := c.MustGet(api.Address).(string) + l := record.NewLogic(c.Request.Context(), ctx) + mid, createTime, err := l.Push("", uid, body) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.SendMsgFailed).SetExtMessage(err.Error())) + return + } + ret := map[string]interface{}{ + "logId": mid, + "datetime": createTime, + } + c.Set(api.ReqResult, ret) + c.Set(api.ReqError, nil) + } +} diff --git a/gateway/api/v1/internal/handler/record/recordhandler.go b/gateway/api/v1/internal/handler/record/recordhandler.go new file mode 100644 index 0000000..dbc62ad --- /dev/null +++ b/gateway/api/v1/internal/handler/record/recordhandler.go @@ -0,0 +1,46 @@ +package record + +import ( + "github.com/gin-gonic/gin" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/logic/record" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/model" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/svc" + "gitlab.33.cn/chat/dtalk/pkg/api" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" +) + +// GetPriRecords +// @Summary 获得聊天记录 +// @Author chy@33.cn +// @Tags record 消息模块 +// @Accept json +// @Produce json +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body model.GetPriRecordsReq false "body" +// @Success 200 {object} model.GeneralResponse{data=model.GetPriRecordsResp} +// @Router /app/pri-chat-record [post] +func GetPriRecords(ctx *svc.ServiceContext) gin.HandlerFunc { + return func(c *gin.Context) { + req := &model.GetPriRecordsReq{} + if err := c.ShouldBind(req); err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + userId, ok := c.Get(api.Address) + if !ok { + c.Set(api.ReqError, xerror.NewError(xerror.SignatureInvalid)) + return + } + req.FromId = userId.(string) + + if req.Mid == "" { + req.Mid = "999999999999999999" + } + + l := record.NewLogic(c.Request.Context(), ctx) + data, err := l.GetPriRecord(req) + c.Set(api.ReqResult, data) + c.Set(api.ReqError, err) + } +} diff --git a/gateway/api/v1/internal/handler/record/revokehandler.go b/gateway/api/v1/internal/handler/record/revokehandler.go new file mode 100644 index 0000000..2eec512 --- /dev/null +++ b/gateway/api/v1/internal/handler/record/revokehandler.go @@ -0,0 +1,49 @@ +package record + +import ( + "github.com/gin-gonic/gin" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/logic/record" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/model" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/svc" + "gitlab.33.cn/chat/dtalk/pkg/api" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" +) + +// RevokeHandler +// @Summary 撤回消息 +// @Description +// @Author dld@33.cn +// @Tags record 消息模块 +// @Accept json +// @Produce json +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body model.RevokeMsgReq true "body" +// @Success 200 {object} model.GeneralResponse{} +// @Router /app/record/revoke [post] +func RevokeHandler(ctx *svc.ServiceContext) gin.HandlerFunc { + return func(c *gin.Context) { + req := &model.RevokeMsgReq{} + if err := c.ShouldBind(req); err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + var err error + operator := c.MustGet(api.Address).(string) + l := record.NewLogic(c.Request.Context(), ctx) + switch req.Type { + case model.Private: + err = l.RevokePersonal(operator, req.Mid) + case model.Group: + err = l.RevokeGroup(operator, req.Mid) + default: + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage("undefined type")) + return + } + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.CodeInnerError).SetExtMessage(err.Error())) + return + } + c.Set(api.ReqResult, nil) + c.Set(api.ReqError, err) + } +} diff --git a/gateway/api/v1/internal/handler/routes.go b/gateway/api/v1/internal/handler/routes.go new file mode 100644 index 0000000..9fa4c11 --- /dev/null +++ b/gateway/api/v1/internal/handler/routes.go @@ -0,0 +1,152 @@ +package handler + +import ( + "net/http" + + "github.com/gin-gonic/gin" + zlog "github.com/rs/zerolog/log" + ginSwagger "github.com/swaggo/gin-swagger" + "github.com/swaggo/gin-swagger/swaggerFiles" + _ "gitlab.33.cn/chat/dtalk/gateway/api/v1/docs" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/handler/account" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/handler/group" + modules "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/handler/modules" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/handler/record" + test "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/handler/test" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/svc" + "gitlab.33.cn/chat/dtalk/pkg/api" + "gitlab.33.cn/chat/dtalk/pkg/api/logger" + "gitlab.33.cn/chat/dtalk/pkg/api/trace" + xlog "gitlab.33.cn/chat/dtalk/pkg/logger" + otrace "gitlab.33.cn/chat/im-pkg/trace" +) + +var ( + serverCtx *svc.ServiceContext + log = zlog.Logger +) + +func Init(ctx *svc.ServiceContext) *http.Server { + serverCtx = ctx + addr := serverCtx.Config().Server.Addr + engine := Default() + SetupEngine(engine) + SetupGroupRoutes(engine) + SetupResourceRoutes(engine) + + srv := &http.Server{ + Addr: addr, + Handler: engine, + } + go func() { + // service connections + if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Error().Err(err).Msg("engineInner.Start() err") + panic(err) + } + }() + return srv +} + +// Default returns an Engine instance with the Logger and Recovery middleware already attached. +func Default() *gin.Engine { + router := gin.New() + // LoggerWithFormatter middleware will write the logs to gin.DefaultWriter + // By default gin.DefaultWriter = os.Stdout + router.Use(gin.LoggerWithFormatter(api.Chat33GinLogFormatter)) + router.Use(gin.Recovery()) + return router +} + +// @title 即时通讯系统后端接口 +// @version 1.0 +// @description +// @termsOfService +// @contact.name +// @contact.url +// @contact.email +// @schemes https +// @host localhost:8080 +// @BasePath /api/v1 +func SetupEngine(e *gin.Engine) *gin.Engine { + root := e.Group("/", otrace.OpenTracingServerMiddleWare(), api.RespMiddleWare()) + root.POST("/test", test.GetHelloWord(serverCtx)) + + userRoute := root.Group("/user") + //获取服务器列表 + userRoute.Use(api.AuthMiddleWare()) + { + userRoute.POST("/login", account.AddressLogin(serverCtx)) + } + + app := root.Group("/app") + { + modulesRoute := app.Group("/modules") + //获取模块启用状态 + modulesRoute.POST("/all", modules.GetModulesHandler(serverCtx)) + + recordRoute := app.Group("/record") + { + recordRoute.POST("/revoke", api.AuthMiddleWare(), record.RevokeHandler(serverCtx)) + recordRoute.POST("/focus", api.AuthMiddleWare(), record.FocusHandler(serverCtx)) + } + } + + recordRoute := root.Group("/record") + recordRoute.Use(api.AuthMiddleWare()) + { + recordRoute.POST("/push", record.PushToUid(serverCtx)) + recordRoute.POST("/push2", record.PushToUid2(serverCtx)) + } + + store := root.Group("/store/app", api.RespMiddleWare()) + store.Use(api.AuthMiddleWare()) + { + store.POST("/pri-chat-record", record.GetPriRecords(serverCtx)) + } + + return e +} + +func SetupGroupRoutes(e *gin.Engine) *gin.Engine { + logMiddleware := logger.NewMiddleware(xlog.New(serverCtx.Config().Env, "group")) + + root := e.Group("/group/app", api.RespMiddleWare()) + root.Use(api.AuthMiddleWare(), trace.TraceMiddleware(), logMiddleware.Handle()) + { + root.POST("/create-group", group.CreateGroupHandler(serverCtx)) + root.POST("/invite-group-members", group.InviteGroupMembersHandler(serverCtx)) + root.POST("/group-exit", group.GroupExitHandler(serverCtx)) + root.POST("/group-disband", group.GroupDisbandHandler(serverCtx)) + root.POST("/group-remove", group.GroupRemoveHandler(serverCtx)) + root.POST("/change-owner", group.ChangeOwnerHandler(serverCtx)) + root.POST("/join-group", group.JoinGroupHandler(serverCtx)) + + root.POST("/name", group.UpdateGroupNameHandler(serverCtx)) + root.POST("/avatar", group.UpdateGroupAvatarHandler(serverCtx)) + root.POST("/joinType", group.UpdateGroupJoinTypeHandler(serverCtx)) + root.POST("/friendType", group.UpdateGroupFriendTypeHandler(serverCtx)) + root.POST("/muteType", group.UpdateGroupMuteTypeHandler(serverCtx)) + root.POST("/member/name", group.UpdateGroupMemberNameHandler(serverCtx)) + root.POST("/member/type", group.SetAdminHandler(serverCtx)) + root.POST("/member/muteTime", group.UpdateGroupMemberMuteTimeHandler(serverCtx)) + + root.POST("/group-info", group.GetGroupInfoHandler(serverCtx)) + root.POST("/group-pub-info", group.GetGroupPubInfoHandler(serverCtx)) + root.POST("/group-list", group.GetGroupListHandler(serverCtx)) + root.POST("/group-member-list", group.GetGroupMemberListHandler(serverCtx)) + root.POST("/group-member-info", group.GetGroupMemberInfoHandler(serverCtx)) + root.POST("/mute-list", group.GetMuteListHandler(serverCtx)) + + } + return e +} + +func SetupResourceRoutes(e *gin.Engine) *gin.Engine { + // swagger 文档接口 + if serverCtx.Config().Env == "debug" { + // todo : 单独开一个 swagger 服务 + e.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) + } + return e +} diff --git a/gateway/api/v1/internal/handler/test/test.go b/gateway/api/v1/internal/handler/test/test.go new file mode 100644 index 0000000..4563793 --- /dev/null +++ b/gateway/api/v1/internal/handler/test/test.go @@ -0,0 +1,17 @@ +package handler + +import ( + "github.com/gin-gonic/gin" + logic "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/logic/test" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/svc" + "gitlab.33.cn/chat/dtalk/pkg/api" +) + +func GetHelloWord(ctx *svc.ServiceContext) gin.HandlerFunc { + return func(c *gin.Context) { + l := logic.NewTestLogic(c.Request.Context(), ctx) + resp, err := l.GetHelloWorld() + c.Set(api.ReqResult, resp) + c.Set(api.ReqError, err) + } +} diff --git a/gateway/api/v1/internal/logic/group/change_owner.go b/gateway/api/v1/internal/logic/group/change_owner.go new file mode 100644 index 0000000..44a25d7 --- /dev/null +++ b/gateway/api/v1/internal/logic/group/change_owner.go @@ -0,0 +1,24 @@ +package logic + +import ( + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/types" + pb "gitlab.33.cn/chat/dtalk/service/group/api" + "gitlab.33.cn/utils/go-kit/convert" +) + +func (l *GroupLogic) ChangeOwner(req *types.ChangeOwnerReq) (*types.ChangeOwnerResp, error) { + groupId := convert.ToInt64(req.Id) + personId := l.getOpe() + memberId := req.MemberId + + _, err := l.svcCtx.GroupClient.ChangeOwner(l.ctx, &pb.ChangeOwnerReq{ + GroupId: groupId, + PersonId: personId, + MemberId: memberId, + }) + if err != nil { + return nil, err + } + + return &types.ChangeOwnerResp{}, nil +} diff --git a/gateway/api/v1/internal/logic/group/create_group.go b/gateway/api/v1/internal/logic/group/create_group.go new file mode 100644 index 0000000..ef7203a --- /dev/null +++ b/gateway/api/v1/internal/logic/group/create_group.go @@ -0,0 +1,51 @@ +package logic + +import ( + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/types" + pb "gitlab.33.cn/chat/dtalk/service/group/api" +) + +func (l *GroupLogic) CreateGroup(req *types.CreateGroupReq) (*types.CreateGroupResp, error) { + name := req.Name + addr := l.getOpe() + owner := &pb.GroupMemberInfo{ + Id: addr, + Name: "", + } + members := make([]*pb.GroupMemberInfo, 0, len(req.MemberIds)) + for _, memberId := range req.MemberIds { + members = append(members, &pb.GroupMemberInfo{ + Id: memberId, + Name: "", + }) + } + + resp1, err := l.svcCtx.GroupClient.CreateGroup(l.ctx, &pb.CreateGroupReq{ + Name: name, + GroupType: pb.GroupType_GROUP_TYPE_NORMAL, + Owner: owner, + Members: members, + }) + if err != nil { + return nil, err + } + + groupId := resp1.GroupId + + resp2, err := l.svcCtx.GroupClient.GetPriGroupInfo(l.ctx, &pb.GetPriGroupInfoReq{ + GroupId: groupId, + PersonId: addr, + DisplayNum: int32(1 + len(members)), + }) + if err != nil { + return nil, err + } + + Group := NewTypesGroupInfo(resp2.Group) + Members := NewTypesGroupMemberInfos(resp2.Group.Members) + + return &types.CreateGroupResp{ + GroupInfo: Group, + Members: Members, + }, nil +} diff --git a/gateway/api/v1/internal/logic/group/get_group_info.go b/gateway/api/v1/internal/logic/group/get_group_info.go new file mode 100644 index 0000000..7f44dbe --- /dev/null +++ b/gateway/api/v1/internal/logic/group/get_group_info.go @@ -0,0 +1,29 @@ +package logic + +import ( + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/types" + pb "gitlab.33.cn/chat/dtalk/service/group/api" +) + +func (l *GroupLogic) GetPriGroupInfo(req *types.GetGroupInfoReq) (*types.GetGroupInfoResp, error) { + if req.DisPlayNum == 0 { + req.DisPlayNum = 10 + } + + resp, err := l.svcCtx.GroupClient.GetPriGroupInfo(l.ctx, &pb.GetPriGroupInfoReq{ + GroupId: req.Id, + PersonId: l.getOpe(), + DisplayNum: int32(req.DisPlayNum), + }) + if err != nil { + return nil, err + } + + Group := NewTypesGroupInfo(resp.Group) + Members := NewTypesGroupMemberInfos(resp.Group.Members) + + return &types.GetGroupInfoResp{ + GroupInfo: Group, + Members: Members, + }, nil +} diff --git a/gateway/api/v1/internal/logic/group/get_group_list.go b/gateway/api/v1/internal/logic/group/get_group_list.go new file mode 100644 index 0000000..7452543 --- /dev/null +++ b/gateway/api/v1/internal/logic/group/get_group_list.go @@ -0,0 +1,23 @@ +package logic + +import ( + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/types" + pb "gitlab.33.cn/chat/dtalk/service/group/api" +) + +func (l *GroupLogic) GetGroupList(req *types.GetGroupListReq) (*types.GetGroupListResp, error) { + personId := l.getOpe() + + resp, err := l.svcCtx.GroupClient.GetGroupList(l.ctx, &pb.GetGroupListReq{ + PersonId: personId, + }) + if err != nil { + return nil, err + } + + Groups := NewTypesGroupInfos(resp.Groups) + + return &types.GetGroupListResp{ + Groups: Groups, + }, nil +} diff --git a/gateway/api/v1/internal/logic/group/get_group_member_info.go b/gateway/api/v1/internal/logic/group/get_group_member_info.go new file mode 100644 index 0000000..b18e5ad --- /dev/null +++ b/gateway/api/v1/internal/logic/group/get_group_member_info.go @@ -0,0 +1,26 @@ +package logic + +import ( + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/types" + pb "gitlab.33.cn/chat/dtalk/service/group/api" +) + +func (l *GroupLogic) GetGroupMemberInfo(req *types.GetGroupMemberInfoReq) (*types.GetGroupMemberInfoResp, error) { + groupId := req.Id + memberId := req.MemberId + + resp, err := l.svcCtx.GroupClient.GetGroupMemberInfo(l.ctx, &pb.GetGroupMemberInfoReq{ + GroupId: groupId, + PersonId: l.getOpe(), + MemberId: memberId, + }) + if err != nil { + return nil, err + } + + Member := NewTypesGroupMemberInfo(resp.Member) + res := &types.GetGroupMemberInfoResp{} + res.GroupMember = Member + + return res, nil +} diff --git a/gateway/api/v1/internal/logic/group/get_group_member_list.go b/gateway/api/v1/internal/logic/group/get_group_member_list.go new file mode 100644 index 0000000..87c3e29 --- /dev/null +++ b/gateway/api/v1/internal/logic/group/get_group_member_list.go @@ -0,0 +1,27 @@ +package logic + +import ( + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/types" + pb "gitlab.33.cn/chat/dtalk/service/group/api" + "gitlab.33.cn/utils/go-kit/convert" +) + +func (l *GroupLogic) GetGroupMemberList(req *types.GetGroupMemberListReq) (*types.GetGroupMemberListResp, error) { + groupId := req.Id + + resp, err := l.svcCtx.GroupClient.GetGroupMemberList(l.ctx, &pb.GetGroupMemberListReq{ + GroupId: groupId, + PersonId: l.getOpe(), + }) + if err != nil { + return nil, err + } + + Members := NewTypesGroupMemberInfos(resp.Members) + + return &types.GetGroupMemberListResp{ + Id: groupId, + IdStr: convert.ToString(groupId), + Members: Members, + }, nil +} diff --git a/gateway/api/v1/internal/logic/group/get_mute_list.go b/gateway/api/v1/internal/logic/group/get_mute_list.go new file mode 100644 index 0000000..da1ee8b --- /dev/null +++ b/gateway/api/v1/internal/logic/group/get_mute_list.go @@ -0,0 +1,19 @@ +package logic + +import ( + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/types" + pb "gitlab.33.cn/chat/dtalk/service/group/api" +) + +func (l *GroupLogic) GetMuteList(req *types.GetMuteListReq) (*types.GetMuteListResp, error) { + resp, err := l.svcCtx.GroupClient.GetMuteList(l.ctx, &pb.GetMuteListReq{ + GroupId: req.Id, + PersonId: l.getOpe(), + }) + if err != nil { + return nil, err + } + + Members := NewTypesGroupMemberInfos(resp.Members) + return &types.GetMuteListResp{Members: Members}, nil +} diff --git a/gateway/api/v1/internal/logic/group/get_pub_group_info.go b/gateway/api/v1/internal/logic/group/get_pub_group_info.go new file mode 100644 index 0000000..75555fd --- /dev/null +++ b/gateway/api/v1/internal/logic/group/get_pub_group_info.go @@ -0,0 +1,21 @@ +package logic + +import ( + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/types" + pb "gitlab.33.cn/chat/dtalk/service/group/api" +) + +func (l *GroupLogic) GetPubGroupInfo(req *types.GetGroupPubInfoReq) (*types.GetGroupPubInfoResp, error) { + resp, err := l.svcCtx.GroupClient.GetPubGroupInfo(l.ctx, &pb.GetPubGroupInfoReq{ + GroupId: req.Id, + PersonId: l.getOpe(), + }) + if err != nil { + return nil, err + } + + res := &types.GetGroupPubInfoResp{} + Group := NewTypesGroupInfo(resp.Group) + res.GroupInfo = Group + return res, nil +} diff --git a/gateway/api/v1/internal/logic/group/group_disband.go b/gateway/api/v1/internal/logic/group/group_disband.go new file mode 100644 index 0000000..910d453 --- /dev/null +++ b/gateway/api/v1/internal/logic/group/group_disband.go @@ -0,0 +1,18 @@ +package logic + +import ( + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/types" + pb "gitlab.33.cn/chat/dtalk/service/group/api" +) + +func (l *GroupLogic) GroupDisband(req *types.GroupDisbandReq) (*types.GroupDisbandResp, error) { + _, err := l.svcCtx.GroupClient.GroupDisband(l.ctx, &pb.GroupDisbandReq{ + GroupId: req.Id, + PersonId: l.getOpe(), + }) + if err != nil { + return nil, err + } + + return &types.GroupDisbandResp{}, nil +} diff --git a/gateway/api/v1/internal/logic/group/group_exit.go b/gateway/api/v1/internal/logic/group/group_exit.go new file mode 100644 index 0000000..481a0ce --- /dev/null +++ b/gateway/api/v1/internal/logic/group/group_exit.go @@ -0,0 +1,18 @@ +package logic + +import ( + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/types" + pb "gitlab.33.cn/chat/dtalk/service/group/api" +) + +func (l *GroupLogic) GroupExit(req *types.GroupExitReq) (*types.GroupExitResp, error) { + _, err := l.svcCtx.GroupClient.GroupExit(l.ctx, &pb.GroupExitReq{ + GroupId: req.Id, + PersonId: l.getOpe(), + }) + if err != nil { + return nil, err + } + + return &types.GroupExitResp{}, nil +} diff --git a/gateway/api/v1/internal/logic/group/group_remove.go b/gateway/api/v1/internal/logic/group/group_remove.go new file mode 100644 index 0000000..734b6b8 --- /dev/null +++ b/gateway/api/v1/internal/logic/group/group_remove.go @@ -0,0 +1,22 @@ +package logic + +import ( + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/types" + pb "gitlab.33.cn/chat/dtalk/service/group/api" +) + +func (l *GroupLogic) GroupRemove(req *types.GroupRemoveReq) (*types.GroupRemoveResp, error) { + resp, err := l.svcCtx.GroupClient.GroupRemove(l.ctx, &pb.GroupRemoveReq{ + GroupId: req.Id, + PersonId: l.getOpe(), + MemberIds: req.MemberIds, + }) + if err != nil { + return nil, err + } + + return &types.GroupRemoveResp{ + MemberNum: resp.MemberNum, + MemberIds: resp.MemberIds, + }, nil +} diff --git a/gateway/api/v1/internal/logic/group/invite_group_members.go b/gateway/api/v1/internal/logic/group/invite_group_members.go new file mode 100644 index 0000000..d0bab7d --- /dev/null +++ b/gateway/api/v1/internal/logic/group/invite_group_members.go @@ -0,0 +1,25 @@ +package logic + +import ( + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/types" + pb "gitlab.33.cn/chat/dtalk/service/group/api" + "gitlab.33.cn/utils/go-kit/convert" +) + +func (l *GroupLogic) InviteGroupMembers(req *types.InviteGroupMembersReq) (*types.InviteGroupMembersResp, error) { + groupId := req.Id + + _, err := l.svcCtx.GroupClient.InviteGroupMembers(l.ctx, &pb.InviteGroupMembersReq{ + GroupId: groupId, + InviterId: l.getOpe(), + MemberIds: req.NewMemberIds, + }) + if err != nil { + return nil, err + } + + return &types.InviteGroupMembersResp{ + Id: groupId, + IdStr: convert.ToString(groupId), + }, nil +} diff --git a/gateway/api/v1/internal/logic/group/join_group.go b/gateway/api/v1/internal/logic/group/join_group.go new file mode 100644 index 0000000..3492d55 --- /dev/null +++ b/gateway/api/v1/internal/logic/group/join_group.go @@ -0,0 +1,25 @@ +package logic + +import ( + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/types" + pb "gitlab.33.cn/chat/dtalk/service/group/api" + "gitlab.33.cn/utils/go-kit/convert" +) + +func (l *GroupLogic) JoinGroup(req *types.JoinGroupReq) (*types.JoinGroupResp, error) { + groupId := req.Id + + _, err := l.svcCtx.GroupClient.InviteGroupMembers(l.ctx, &pb.InviteGroupMembersReq{ + GroupId: groupId, + InviterId: req.InviterId, + MemberIds: []string{l.getOpe()}, + }) + if err != nil { + return nil, err + } + + return &types.JoinGroupResp{ + Id: groupId, + IdStr: convert.ToString(groupId), + }, nil +} diff --git a/gateway/api/v1/internal/logic/group/newlogic.go b/gateway/api/v1/internal/logic/group/newlogic.go new file mode 100644 index 0000000..1939b57 --- /dev/null +++ b/gateway/api/v1/internal/logic/group/newlogic.go @@ -0,0 +1,96 @@ +package logic + +import ( + "context" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/svc" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/types" + "gitlab.33.cn/chat/dtalk/pkg/api" + pb "gitlab.33.cn/chat/dtalk/service/group/api" + "gitlab.33.cn/utils/go-kit/convert" +) + +type GroupLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGroupLogic(ctx context.Context, svcCtx *svc.ServiceContext) GroupLogic { + return GroupLogic{ + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GroupLogic) getOpe() string { + return api.NewAddrWithContext(l.ctx) +} + +func NewTypesGroupInfo(do *pb.GroupBizInfo) *types.GroupInfo { + if do == nil { + return &types.GroupInfo{} + } + + res := &types.GroupInfo{ + Id: do.Id, + IdStr: convert.ToString(do.Id), + MarkId: do.MarkId, + Name: do.Name, + PublicName: do.PubName, + Avatar: do.Avatar, + Introduce: do.Introduce, + Owner: nil, + Person: nil, + MemberNum: do.MemberNum, + Maximum: do.MemberMaximum, + Status: int32(do.Status), + CreateTime: do.CreateTime, + JoinType: int32(do.JoinType), + MuteType: int32(do.MuteType), + FriendType: int32(do.FriendType), + MuteNum: do.MuteNum, + AdminNum: do.AdminNum, + AESKey: do.AESKey, + GroupType: int32(do.GetType()), + } + + if do.Owner != nil { + res.Owner = NewTypesGroupMemberInfo(do.Owner) + } + + if do.Person != nil { + res.Person = NewTypesGroupMemberInfo(do.Person) + } + + return res +} + +func NewTypesGroupInfos(dos []*pb.GroupBizInfo) []*types.GroupInfo { + dtos := make([]*types.GroupInfo, 0, len(dos)) + for _, do := range dos { + dtos = append(dtos, NewTypesGroupInfo(do)) + } + + return dtos +} + +func NewTypesGroupMemberInfo(do *pb.GroupMemberBizInfo) *types.GroupMember { + if do == nil { + return &types.GroupMember{} + } + + return &types.GroupMember{ + MemberId: do.Id, + MemberName: do.Name, + MemberType: int32(do.Type), + MemberMuteTime: do.MuteTime, + } +} + +func NewTypesGroupMemberInfos(dos []*pb.GroupMemberBizInfo) []*types.GroupMember { + dtos := make([]*types.GroupMember, 0, len(dos)) + for _, do := range dos { + dtos = append(dtos, NewTypesGroupMemberInfo(do)) + } + + return dtos +} diff --git a/gateway/api/v1/internal/logic/group/set_admin.go b/gateway/api/v1/internal/logic/group/set_admin.go new file mode 100644 index 0000000..b5a8010 --- /dev/null +++ b/gateway/api/v1/internal/logic/group/set_admin.go @@ -0,0 +1,25 @@ +package logic + +import ( + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/types" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + pb "gitlab.33.cn/chat/dtalk/service/group/api" +) + +func (l *GroupLogic) SetAdmin(req *types.SetAdminReq) (*types.SetAdminResp, error) { + if _, ok := pb.GroupMemberType_name[req.MemberType]; !ok { + return nil, xerror.NewError(xerror.ParamsError) + } + + _, err := l.svcCtx.GroupClient.SetAdmin(l.ctx, &pb.SetAdminReq{ + GroupId: req.Id, + PersonId: l.getOpe(), + MemberId: req.MemberId, + GroupMemberType: pb.GroupMemberType(req.MemberType), + }) + if err != nil { + return nil, err + } + + return &types.SetAdminResp{}, nil +} diff --git a/gateway/api/v1/internal/logic/group/update_group_avatar.go b/gateway/api/v1/internal/logic/group/update_group_avatar.go new file mode 100644 index 0000000..dfd8a64 --- /dev/null +++ b/gateway/api/v1/internal/logic/group/update_group_avatar.go @@ -0,0 +1,19 @@ +package logic + +import ( + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/types" + pb "gitlab.33.cn/chat/dtalk/service/group/api" +) + +func (l *GroupLogic) UpdateGroupAvatar(req *types.UpdateGroupAvatarReq) (*types.UpdateGroupAvatarResp, error) { + _, err := l.svcCtx.GroupClient.UpdateGroupAvatar(l.ctx, &pb.UpdateGroupAvatarReq{ + GroupId: req.Id, + PersonId: l.getOpe(), + Avatar: req.Avatar, + }) + if err != nil { + return nil, err + } + + return &types.UpdateGroupAvatarResp{}, nil +} diff --git a/gateway/api/v1/internal/logic/group/update_group_friend_type.go b/gateway/api/v1/internal/logic/group/update_group_friend_type.go new file mode 100644 index 0000000..944b5d6 --- /dev/null +++ b/gateway/api/v1/internal/logic/group/update_group_friend_type.go @@ -0,0 +1,19 @@ +package logic + +import ( + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/types" + pb "gitlab.33.cn/chat/dtalk/service/group/api" +) + +func (l *GroupLogic) UpdateGroupFriendType(req *types.UpdateGroupFriendTypeReq) (*types.UpdateGroupFriendTypeResp, error) { + _, err := l.svcCtx.GroupClient.UpdateGroupFriendType(l.ctx, &pb.UpdateGroupFriendTypeReq{ + GroupId: req.Id, + PersonId: l.getOpe(), + GroupFriendType: pb.GroupFriendType(req.FriendType), + }) + if err != nil { + return nil, err + } + + return &types.UpdateGroupFriendTypeResp{}, nil +} diff --git a/gateway/api/v1/internal/logic/group/update_group_join_type.go b/gateway/api/v1/internal/logic/group/update_group_join_type.go new file mode 100644 index 0000000..a5cb32f --- /dev/null +++ b/gateway/api/v1/internal/logic/group/update_group_join_type.go @@ -0,0 +1,19 @@ +package logic + +import ( + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/types" + pb "gitlab.33.cn/chat/dtalk/service/group/api" +) + +func (l *GroupLogic) UpdateGroupJoinType(req *types.UpdateGroupJoinTypeReq) (*types.UpdateGroupJoinTypeResp, error) { + _, err := l.svcCtx.GroupClient.UpdateGroupJoinType(l.ctx, &pb.UpdateGroupJoinTypeReq{ + GroupId: req.Id, + PersonId: l.getOpe(), + GroupJoinType: pb.GroupJoinType(req.JoinType), + }) + if err != nil { + return nil, err + } + + return &types.UpdateGroupJoinTypeResp{}, nil +} diff --git a/gateway/api/v1/internal/logic/group/update_group_member_mute_time.go b/gateway/api/v1/internal/logic/group/update_group_member_mute_time.go new file mode 100644 index 0000000..eb1efc5 --- /dev/null +++ b/gateway/api/v1/internal/logic/group/update_group_member_mute_time.go @@ -0,0 +1,22 @@ +package logic + +import ( + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/types" + pb "gitlab.33.cn/chat/dtalk/service/group/api" +) + +func (l *GroupLogic) UpdateGroupMemberMuteTime(req *types.UpdateGroupMemberMuteTimeReq) (*types.UpdateGroupMemberMuteTimeResp, error) { + resp, err := l.svcCtx.GroupClient.UpdateGroupMemberMuteTime(l.ctx, &pb.UpdateGroupMemberMuteTimeReq{ + GroupId: req.Id, + PersonId: l.getOpe(), + MemberIds: req.MemberIds, + MuteTime: req.MuteTime, + }) + if err != nil { + return nil, err + } + + Members := NewTypesGroupMemberInfos(resp.Members) + + return &types.UpdateGroupMemberMuteTimeResp{Members: Members}, nil +} diff --git a/gateway/api/v1/internal/logic/group/update_group_member_name.go b/gateway/api/v1/internal/logic/group/update_group_member_name.go new file mode 100644 index 0000000..a2168f9 --- /dev/null +++ b/gateway/api/v1/internal/logic/group/update_group_member_name.go @@ -0,0 +1,19 @@ +package logic + +import ( + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/types" + pb "gitlab.33.cn/chat/dtalk/service/group/api" +) + +func (l *GroupLogic) UpdateGroupMemberName(req *types.UpdateGroupMemberNameReq) (*types.UpdateGroupMemberNameResp, error) { + _, err := l.svcCtx.GroupClient.UpdateGroupMemberName(l.ctx, &pb.UpdateGroupMemberNameReq{ + GroupId: req.Id, + PersonId: l.getOpe(), + MemberName: req.MemberName, + }) + if err != nil { + return nil, err + } + + return &types.UpdateGroupMemberNameResp{}, nil +} diff --git a/gateway/api/v1/internal/logic/group/update_group_mute_type.go b/gateway/api/v1/internal/logic/group/update_group_mute_type.go new file mode 100644 index 0000000..485b285 --- /dev/null +++ b/gateway/api/v1/internal/logic/group/update_group_mute_type.go @@ -0,0 +1,19 @@ +package logic + +import ( + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/types" + pb "gitlab.33.cn/chat/dtalk/service/group/api" +) + +func (l *GroupLogic) UpdateGroupMuteType(req *types.UpdateGroupMuteTypeReq) (*types.UpdateGroupMuteTypeResp, error) { + _, err := l.svcCtx.GroupClient.UpdateGroupMuteType(l.ctx, &pb.UpdateGroupMuteTypeReq{ + GroupId: req.Id, + PersonId: l.getOpe(), + GroupMuteType: pb.GroupMuteType(req.MuteType), + }) + if err != nil { + return nil, err + } + + return &types.UpdateGroupMuteTypeResp{}, nil +} diff --git a/gateway/api/v1/internal/logic/group/update_group_name.go b/gateway/api/v1/internal/logic/group/update_group_name.go new file mode 100644 index 0000000..4ca98cd --- /dev/null +++ b/gateway/api/v1/internal/logic/group/update_group_name.go @@ -0,0 +1,20 @@ +package logic + +import ( + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/types" + pb "gitlab.33.cn/chat/dtalk/service/group/api" +) + +func (l *GroupLogic) UpdateGroupName(req *types.UpdateGroupNameReq) (*types.UpdateGroupNameResp, error) { + _, err := l.svcCtx.GroupClient.UpdateGroupName(l.ctx, &pb.UpdateGroupNameReq{ + GroupId: req.Id, + PersonId: l.getOpe(), + Name: req.Name, + PublicName: req.PublicName, + }) + if err != nil { + return nil, err + } + + return &types.UpdateGroupNameResp{}, nil +} diff --git a/gateway/api/v1/internal/logic/record/focus.go b/gateway/api/v1/internal/logic/record/focus.go new file mode 100644 index 0000000..3bec5c8 --- /dev/null +++ b/gateway/api/v1/internal/logic/record/focus.go @@ -0,0 +1,87 @@ +package record + +import ( + "time" + + xproto "github.com/golang/protobuf/proto" + "github.com/pkg/errors" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/model" + "gitlab.33.cn/chat/dtalk/pkg/util" + "gitlab.33.cn/chat/dtalk/service/group/model/biz" + "gitlab.33.cn/chat/imparse/proto" +) + +func (l *Logic) FocusPersonal(Operator string, logId int64) error { + //查找消息 + rd, err := l.svcCtx.StoreClient.GetRecord(l.ctx, proto.Channel_ToUser, logId) + if err != nil { + return err + } + target := rd.ReceiverId + sender := rd.SenderId + if Operator == sender || Operator != target { + return model.ErrPermission + } + now := uint64(util.TimeNowUnixNano() / int64(time.Millisecond)) + num, err := l.svcCtx.StoreClient.AddRecordFocus(l.ctx, Operator, logId, now) + if err != nil { + return err + } + action := &proto.SignalFocusMessage{ + Mid: logId, + Uid: Operator, + CurrentNum: num, + Time: now, + } + body, err := xproto.Marshal(action) + if err != nil { + return errors.WithMessagef(err, "proto.Marshal, action=%+v", action) + } + err = l.svcCtx.AnswerClient.UniCastSignal(l.ctx, proto.SignalType_FocusMessage, sender, body) + if err != nil { + return err + } + return l.svcCtx.AnswerClient.UniCastSignal(l.ctx, proto.SignalType_FocusMessage, target, body) +} + +func (l *Logic) FocusGroup(Operator string, logId int64) error { + //查找消息 + rd, err := l.svcCtx.StoreClient.GetRecord(l.ctx, proto.Channel_ToGroup, logId) + if err != nil { + return err + } + target := rd.ReceiverId + sender := rd.SenderId + if Operator == sender { + return model.ErrPermission + } + //群成员判断 + memOpt, err := l.svcCtx.GroupClient.GetMember(l.ctx, util.ToInt64(target), Operator) + if err != nil || memOpt == nil { + return err + } + switch memOpt.GroupMemberType { + case biz.GroupMemberTypeOwner: + case biz.GroupMemberTypeAdmin: + case biz.GroupMemberTypeNormal: + default: + return model.ErrPermission + } + + now := uint64(util.TimeNowUnixNano() / int64(time.Millisecond)) + num, err := l.svcCtx.StoreClient.AddRecordFocus(l.ctx, Operator, logId, now) + if err != nil { + return err + } + action := &proto.SignalFocusMessage{ + Mid: logId, + Uid: Operator, + CurrentNum: num, + Time: now, + } + body, err := xproto.Marshal(action) + if err != nil { + return errors.WithMessagef(err, "proto.Marshal, action=%+v", action) + } + return l.svcCtx.AnswerClient.GroupCastSignal(l.ctx, proto.SignalType_FocusMessage, target, body) +} diff --git a/gateway/api/v1/internal/logic/record/push.go b/gateway/api/v1/internal/logic/record/push.go new file mode 100644 index 0000000..b448a06 --- /dev/null +++ b/gateway/api/v1/internal/logic/record/push.go @@ -0,0 +1,5 @@ +package record + +func (l *Logic) Push(key, from string, body []byte) (int64, uint64, error) { + return l.svcCtx.AnswerClient.PushCommonMsg(l.ctx, key, from, body) +} diff --git a/gateway/api/v1/internal/logic/record/record.go b/gateway/api/v1/internal/logic/record/record.go new file mode 100644 index 0000000..bab48ee --- /dev/null +++ b/gateway/api/v1/internal/logic/record/record.go @@ -0,0 +1,47 @@ +package record + +import ( + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/model" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/pkg/util" + store "gitlab.33.cn/chat/dtalk/service/record/store/api" + storeModel "gitlab.33.cn/chat/dtalk/service/record/store/model" + "gitlab.33.cn/chat/imparse/proto" +) + +func (l *Logic) GetPriRecord(req *model.GetPriRecordsReq) (*model.GetPriRecordsResp, error) { + resp, err := l.svcCtx.StoreClient.GetRecordsAfterMid(l.ctx, &store.GetRecordsAfterMidReq{ + Tp: proto.Channel_ToUser, + Mid: util.ToInt64(req.Mid), + From: req.FromId, + Target: req.TargetId, + Count: req.RecordCount, + }) + if err != nil { + return nil, xerror.NewError(xerror.QueryFailed).SetExtMessage(err.Error()) + } + + res := &model.GetPriRecordsResp{ + RecordCount: len(resp.Records), + Records: toChatRecord(resp.Records), + } + + return res, nil +} + +// toChatRecord []MsgContent -> []Record , content json -> proto +func toChatRecord(msgs []*store.GetRecordReply) []*model.Record { + Result := make([]*model.Record, len(msgs)) + for i, msg := range msgs { + Result[i] = &model.Record{ + Mid: msg.Mid, + Seq: msg.Seq, + FromId: msg.SenderId, + TargetId: msg.ReceiverId, + MsgType: int32(msg.MsgType), + Content: storeModel.JsonUnmarshal(msg.MsgType, []byte(msg.Content)), + CreateTime: msg.CreateTime, + } + } + return Result +} diff --git a/gateway/api/v1/internal/logic/record/revoke.go b/gateway/api/v1/internal/logic/record/revoke.go new file mode 100644 index 0000000..6577696 --- /dev/null +++ b/gateway/api/v1/internal/logic/record/revoke.go @@ -0,0 +1,101 @@ +package record + +import ( + "context" + "gitlab.33.cn/chat/dtalk/service/group/model/biz" + "time" + + xproto "github.com/golang/protobuf/proto" + "github.com/pkg/errors" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/model" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/svc" + "gitlab.33.cn/chat/dtalk/pkg/util" + "gitlab.33.cn/chat/imparse/proto" +) + +type Logic struct { + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewLogic(ctx context.Context, svcCtx *svc.ServiceContext) Logic { + return Logic{ + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *Logic) RevokePersonal(Operator string, mid int64) error { + //查找消息 + rd, err := l.svcCtx.StoreClient.GetRecord(l.ctx, proto.Channel_ToUser, mid) + if err != nil { + return err + } + target := rd.ReceiverId + if rd.SenderId != Operator || time.Since(util.UnixToTime(int64(rd.CreateTime))) > time.Duration(l.svcCtx.Config().Revoke.Expire) { + return model.ErrPermission + } + action := &proto.SignalRevoke{ + Mid: mid, + Operator: Operator, + Self: rd.SenderId == Operator, + } + body, err := xproto.Marshal(action) + if err != nil { + return errors.WithMessagef(err, "proto.Marshal, action=%+v", action) + } + err = l.svcCtx.AnswerClient.UniCastSignal(l.ctx, proto.SignalType_Revoke, target, body) + if err != nil { + return err + } + if err := l.svcCtx.StoreClient.DelRecord(l.ctx, proto.Channel_ToUser, mid); err != nil { + return err + } + return l.svcCtx.AnswerClient.UniCastSignal(l.ctx, proto.SignalType_Revoke, Operator, body) +} + +func (l *Logic) RevokeGroup(Operator string, mid int64) error { + //查找消息 + rd, err := l.svcCtx.StoreClient.GetRecord(l.ctx, proto.Channel_ToGroup, mid) + if err != nil { + return err + } + target := rd.ReceiverId + if rd.SenderId == Operator && time.Since(util.UnixToTime(int64(rd.CreateTime))) > time.Duration(l.svcCtx.Config().Revoke.Expire) { + return model.ErrPermission + } + if rd.SenderId != Operator { + //执行者 + memOpt, err := l.svcCtx.GroupClient.GetMember(l.ctx, util.ToInt64(target), Operator) + if err != nil || memOpt == nil { + return err + } + //消息所有者 + memOwn, err := l.svcCtx.GroupClient.GetMember(l.ctx, util.ToInt64(target), rd.SenderId) + if err != nil || memOwn == nil { + return err + } + switch memOpt.GroupMemberType { + case biz.GroupMemberTypeOwner: + case biz.GroupMemberTypeAdmin: + if memOwn.GroupMemberType == biz.GroupMemberTypeOwner { + return model.ErrPermission + } + default: + return model.ErrPermission + } + } + action := &proto.SignalRevoke{ + Mid: mid, + Operator: Operator, + Self: rd.SenderId == Operator, + } + body, err := xproto.Marshal(action) + if err != nil { + return errors.WithMessagef(err, "proto.Marshal, action=%+v", action) + } + if err := l.svcCtx.StoreClient.DelRecord(l.ctx, proto.Channel_ToGroup, mid); err != nil { + return err + } + return l.svcCtx.AnswerClient.GroupCastSignal(l.ctx, proto.SignalType_Revoke, target, body) +} diff --git a/gateway/api/v1/internal/logic/test/testlogic.go b/gateway/api/v1/internal/logic/test/testlogic.go new file mode 100644 index 0000000..ea58793 --- /dev/null +++ b/gateway/api/v1/internal/logic/test/testlogic.go @@ -0,0 +1,24 @@ +package logic + +import ( + "context" + + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/svc" + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/types" +) + +type TestLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewTestLogic(ctx context.Context, svcCtx *svc.ServiceContext) TestLogic { + return TestLogic{ + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *TestLogic) GetHelloWorld() (*types.Hello, error) { + return &types.Hello{Content: "hello world"}, nil +} diff --git a/gateway/api/v1/internal/model/account.go b/gateway/api/v1/internal/model/account.go new file mode 100644 index 0000000..7394846 --- /dev/null +++ b/gateway/api/v1/internal/model/account.go @@ -0,0 +1,7 @@ +package model + +// 用户地址登录请求结果 +type AddressLoginResp struct { + // 用户地址 + Address string `json:"address" example:"123"` +} diff --git a/gateway/api/v1/internal/model/const.go b/gateway/api/v1/internal/model/const.go new file mode 100644 index 0000000..feabb0f --- /dev/null +++ b/gateway/api/v1/internal/model/const.go @@ -0,0 +1,6 @@ +package model + +const ( + Private = 0 + Group = 1 +) diff --git a/gateway/api/v1/internal/model/error.go b/gateway/api/v1/internal/model/error.go new file mode 100644 index 0000000..99e84be --- /dev/null +++ b/gateway/api/v1/internal/model/error.go @@ -0,0 +1,7 @@ +package model + +import "errors" + +var ( + ErrPermission = errors.New("权限不足") +) diff --git a/gateway/api/v1/internal/model/general.go b/gateway/api/v1/internal/model/general.go new file mode 100644 index 0000000..40ea484 --- /dev/null +++ b/gateway/api/v1/internal/model/general.go @@ -0,0 +1,7 @@ +package model + +type GeneralResponse struct { + Result int `json:"result"` + Message string `json:"message"` + Data interface{} `json:"data"` +} diff --git a/gateway/api/v1/internal/model/modules.go b/gateway/api/v1/internal/model/modules.go new file mode 100644 index 0000000..c736953 --- /dev/null +++ b/gateway/api/v1/internal/model/modules.go @@ -0,0 +1,7 @@ +package model + +type GetModuleResp struct { + Name string `json:"name" enums:"wallet,oa,redpacket"` + IsEnabled bool `json:"isEnabled"` + EndPoints []string `json:"endPoints"` +} diff --git a/gateway/api/v1/internal/model/record.go b/gateway/api/v1/internal/model/record.go new file mode 100644 index 0000000..521a355 --- /dev/null +++ b/gateway/api/v1/internal/model/record.go @@ -0,0 +1,50 @@ +package model + +type RevokeMsgReq struct { + Type int `json:"type" enums:"0,1"` + Mid int64 `json:"logId" binding:"required"` +} + +type FocusMsgReq struct { + Type int `json:"type" enums:"0,1"` + LogId int64 `json:"logId" binding:"required"` +} + +type Record struct { + // log id + Mid string `json:"logId"` + // msg id (uuid) + Seq string `json:"msgId"` + // 发送者 id + FromId string `json:"fromId"` + // 接收者 id + TargetId string `json:"targetId"` + // 消息类型 + MsgType int32 `json:"msgType"` + // 消息内容 + Content interface{} `json:"content"` + // 消息发送时间 + CreateTime uint64 `json:"createTime"` +} + +type GetPriRecordsReq struct { + // 发送者 ID + FromId string `json:"-"` + + // 发送者 ID + //FromId string `json:"fromId" binding:"required"` + + // 接受者 ID + TargetId string `json:"targetId" binding:"required"` + // 消息数量 + RecordCount int64 `json:"count" binding:"required,min=1,max=100"` + // 消息 ID + Mid string `json:"logId"` +} + +type GetPriRecordsResp struct { + // 聊天记录数量 + RecordCount int `json:"record_count"` + // 聊天记录 + Records []*Record `json:"records"` +} diff --git a/gateway/api/v1/internal/svc/servicecontext.go b/gateway/api/v1/internal/svc/servicecontext.go new file mode 100644 index 0000000..6100257 --- /dev/null +++ b/gateway/api/v1/internal/svc/servicecontext.go @@ -0,0 +1,48 @@ +package svc + +import ( + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/pkg/interceptor/trace" + group "gitlab.33.cn/chat/dtalk/service/group/api" + store "gitlab.33.cn/chat/dtalk/service/record/store/api" + "google.golang.org/grpc" + "sync" + "time" + + "gitlab.33.cn/chat/dtalk/gateway/api/v1/internal/config" + answer "gitlab.33.cn/chat/dtalk/service/record/answer/api" +) + +// ServiceContext 服务上下文 +type ServiceContext struct { + m sync.RWMutex + c config.Config + + AnswerClient *answer.Client + StoreClient *store.Client + GroupClient *group.Client +} + +func NewServiceContext(c config.Config) *ServiceContext { + sc := &ServiceContext{ + c: c, + AnswerClient: answer.New(c.AnswerRPCClient.RegAddrs, c.AnswerRPCClient.Schema, c.AnswerRPCClient.SrvName, time.Duration(c.AnswerRPCClient.Dial)), + StoreClient: store.New(c.StoreRPCClient.RegAddrs, c.StoreRPCClient.Schema, c.StoreRPCClient.SrvName, time.Duration(c.StoreRPCClient.Dial)), + GroupClient: group.New(c.GroupRPCClient.RegAddrs, + c.GroupRPCClient.Schema, + c.GroupRPCClient.SrvName, + time.Duration(c.GroupRPCClient.Dial), + grpc.WithChainUnaryInterceptor(xerror.ErrClientInterceptor, trace.UnaryClientInterceptor), + ), + } + + return sc +} + +// Config 获取全局配置 +func (sc *ServiceContext) Config() config.Config { + sc.m.RLock() + defer sc.m.RUnlock() + + return sc.c +} diff --git a/gateway/api/v1/internal/types/group.go b/gateway/api/v1/internal/types/group.go new file mode 100644 index 0000000..c38d8a9 --- /dev/null +++ b/gateway/api/v1/internal/types/group.go @@ -0,0 +1,431 @@ +package types + +type GeneralResp struct { + Result int `json:"result"` + Message int `json:"message"` + Data interface{} `json:"data"` +} + +type GroupMember struct { + // 用户 ID + MemberId string `json:"memberId" form:"memberId"` + // 用户群昵称 + MemberName string `json:"memberName" form:"memberName"` + // 用户角色,2=群主,1=管理员,0=群员,10=退群 + MemberType int32 `json:"memberType" form:"memberType"` + // 该用户被禁言结束的时间 9223372036854775807=永久禁言 + MemberMuteTime int64 `json:"memberMuteTime"` +} + +type GroupInfo struct { + // 群 ID + Id int64 `json:"id" form:"id"` + IdStr string `json:"idStr"` + // 群显示的 ID + MarkId string `json:"markId" form:"markId"` + // 群名称 加密的 + Name string `json:"name" form:"name"` + // 公开的群名称 不加密的 + PublicName string `json:"publicName"` + // 头像 url + Avatar string `json:"avatar" form:"avatar"` + Introduce string `json:"introduce" form:"introduce"` + // 群主 信息 + Owner *GroupMember `json:"owner" form:"owner"` + // 本人在群内的信息 + Person *GroupMember `json:"person" form:"person"` + // 群人数 + MemberNum int32 `json:"memberNum" form:"memberNum"` + // 群人数上限 + Maximum int32 `json:"maximum" form:"maximum"` + // 群状态,0=正常 1=封禁 2=解散 + Status int32 `json:"status" form:"status"` + // 群创建时间 + CreateTime int64 `json:"createTime" form:"createTime"` + // 加群方式,0=无需审批(默认),1=禁止加群,群主和管理员邀请加群, 2=普通人邀请需要审批,群主和管理员直接加群 + JoinType int32 `json:"joinType" form:"joinType"` + // 禁言, 0=全员可发言, 1=全员禁言(除群主和管理员) + MuteType int32 `json:"muteType" form:"muteType"` + // 加好友限制, 0=群内可加好友,1=群内禁止加好友 + FriendType int32 `json:"friendType"` + // 群内当前被禁言的人数 + MuteNum int32 `json:"muteNum"` + // 群内管理员数量 + AdminNum int32 `json:"adminNum"` + // + AESKey string `json:"key"` + // 群类型 (0: 普通群, 1: 全员群, 2: 部门群) + GroupType int32 `json:"groupType"` +} + +type CreateGroupReq struct { + Name string `json:"name" form:"name"` + Avatar string `json:"avatar" form:"avatar"` + Introduce string `json:"introduce" form:"introduce"` + MemberIds []string `json:"memberIds" form:"memberIds"` +} + +type CreateGroupResp struct { + *GroupInfo + // 群成员 + Members []*GroupMember `json:"members" form:"members"` +} + +type InviteGroupMembersReq struct { + Id int64 `json:"id" form:"id"` + // 如果同时填了 idStr, 则优先选择 idStr + IdStr string `json:"idStr"` + Inviter GroupMember `json:"-"` + NewMembers []GroupMember `json:"-"` + NewMemberIds []string `json:"newMemberIds" form:"newMemberIds" binding:"required"` +} + +type InviteGroupMembersResp struct { + Id int64 `json:"id" form:"id" example:"123821199217135616"` + IdStr string `json:"idStr"` + MemberNum int32 `json:"memberNum" form:"memberNum" example:"5"` + //Inviter GroupMember `json:"inviter" form:"inviter"` + //NewMembers []GroupMember `json:"newMembers" form:"newMembers"` +} + +type GetGroupInfoReq struct { + Id int64 `json:"id" uri:"id"` + // 如果同时填了 idStr, 则优先选择 idStr + IdStr string `json:"idStr"` + PersonId string `json:"-"` + DisPlayNum int64 `json:"-"` +} + +type GetGroupInfoResp struct { + *GroupInfo + Members []*GroupMember `json:"members" form:"members"` +} + +type GetGroupListReq struct { + PersonId string `json:"-"` +} + +type GetGroupListResp struct { + Groups []*GroupInfo `json:"groups"` +} + +type GetGroupMemberListReq struct { + Id int64 `json:"id" uri:"id"` + // 如果同时填了 idStr, 则优先选择 idStr + IdStr string `json:"idStr"` + PersonId string `json:"-"` +} + +type GetGroupMemberListResp struct { + Id int64 `json:"id"` + // 如果同时填了 idStr, 则优先选择 idStr + IdStr string `json:"idStr"` + Members []*GroupMember `json:"members"` +} + +type GetGroupMemberInfoReq struct { + Id int64 `json:"id" uri:"id"` + // 如果同时填了 idStr, 则优先选择 idStr + IdStr string `json:"idStr"` + MemberId string `json:"memberId" uri:"memberId" binding:"required"` + PersonId string `json:"-"` +} + +type GetGroupMemberInfoResp struct { + *GroupMember +} + +type GroupExitReq struct { + Id int64 `json:"id"` + // 如果同时填了 idStr, 则优先选择 idStr + IdStr string `json:"idStr"` + PersonId string `json:"-"` +} + +type GroupExitResp struct { +} + +type GroupRemoveReq struct { + Id int64 `json:"id"` + // 如果同时填了 idStr, 则优先选择 idStr + IdStr string `json:"idStr"` + MemberIds []string `json:"memberIds" binding:"required"` + PersonId string `json:"-"` +} + +type GroupRemoveResp struct { + // 群人数 + MemberNum int32 `json:"memberNum" form:"memberNum"` + // 成功被踢的成员列表 + MemberIds []string `json:"memberIds"` +} + +type GroupDisbandReq struct { + Id int64 `json:"id"` + // 如果同时填了 idStr, 则优先选择 idStr + IdStr string `json:"idStr"` + PersonId string `json:"-"` +} + +type GroupDisbandResp struct { +} + +type UpdateGroupNameReq struct { + Id int64 `json:"id"` + // 如果同时填了 idStr, 则优先选择 idStr + IdStr string `json:"idStr"` + PersonId string `json:"-"` + Name string `json:"name"` + PublicName string `json:"publicName"` +} + +type UpdateGroupNameResp struct { +} + +type UpdateGroupAvatarReq struct { + Id int64 `json:"id"` + // 如果同时填了 idStr, 则优先选择 idStr + IdStr string `json:"idStr"` + PersonId string `json:"-"` + Avatar string `json:"avatar"` +} + +type UpdateGroupAvatarResp struct { +} + +type UpdateGroupMemberNameReq struct { + Id int64 `json:"id"` + // 如果同时填了 idStr, 则优先选择 idStr + IdStr string `json:"idStr"` + PersonId string `json:"-"` + MemberName string `json:"memberName"` +} + +type UpdateGroupMemberNameResp struct { +} + +type UpdateGroupJoinTypeReq struct { + // 群 ID + Id int64 `json:"id"` + // 如果同时填了 idStr, 则优先选择 idStr + IdStr string `json:"idStr"` + PersonId string `json:"-"` + // 加群方式,0=无需审批(默认),1=禁止加群,群主和管理员邀请加群, 2=普通人邀请需要审批,群主和管理员直接加群 + JoinType int32 `json:"joinType" binding:"oneof=0 1 2"` +} + +type UpdateGroupJoinTypeResp struct { +} + +type UpdateGroupFriendTypeReq struct { + // 群 ID + Id int64 `json:"id"` + // 如果同时填了 idStr, 则优先选择 idStr + IdStr string `json:"idStr"` + PersonId string `json:"-"` + // 加好友限制, 0=群内可加好友,1=群内禁止加好友 + FriendType int32 `json:"friendType" binding:"oneof=0 1"` +} + +type UpdateGroupFriendTypeResp struct { +} + +type ChangeOwnerReq struct { + // 群 ID + Id int64 `json:"id"` + // 如果同时填了 idStr, 则优先选择 idStr + IdStr string `json:"idStr"` + // 被转让为群主的群成员 ID + MemberId string `json:"memberId" binding:"required"` + PersonId string `json:"-"` +} + +type ChangeOwnerResp struct { +} + +// JoinGroupReq 扫二维码加群 +type JoinGroupReq struct { + Id int64 `json:"id"` + IdStr string `json:"idStr"` + InviterId string `json:"inviterId"` + PersonId string `json:"-"` +} + +type JoinGroupResp struct { + Id int64 `json:"id"` + IdStr string `json:"idStr"` +} + +type SetAdminReq struct { + // 群 ID + Id int64 `json:"id"` + // 如果同时填了 idStr, 则优先选择 idStr + IdStr string `json:"idStr"` + // 被设置的群成员 ID + MemberId string `json:"memberId" binding:"required"` + PersonId string `json:"-"` + // 用户角色 0=群员, 1=管理员 + MemberType int32 `json:"memberType" binding:"oneof=0 1"` +} + +type SetAdminResp struct { +} + +type UpdateGroupMuteTypeReq struct { + // 群 ID + Id int64 `json:"id"` + // 如果同时填了 idStr, 则优先选择 idStr + IdStr string `json:"idStr"` + // 禁言, 0=全员可发言, 1=全员禁言(除群主和管理员) + MuteType int32 `json:"muteType" binding:"oneof=0 1"` + PersonId string `json:"-"` +} + +type UpdateGroupMuteTypeResp struct { +} + +type UpdateGroupMemberMuteTimeReq struct { + // 群 ID + Id int64 `json:"id"` + // 如果同时填了 idStr, 则优先选择 idStr + IdStr string `json:"idStr"` + // 被禁言的群员 ID + MemberIds []string `json:"memberIds" binding:"required"` + // 禁言持续时间, 传9223372036854775807=永久禁言, 0=解除禁言 + MuteTime int64 `json:"muteTime"` + PersonId string `json:"-"` +} + +type UpdateGroupMemberMuteTimeResp struct { + Members []*GroupMember `json:"members"` +} + +type GetMuteListReq struct { + // 群 ID + Id int64 `json:"id"` + // 如果同时填了 idStr, 则优先选择 idStr + IdStr string `json:"idStr"` + PersonId string `json:"-"` +} + +type GetMuteListResp struct { + Members []*GroupMember `json:"members"` +} + +type GetGroupPubInfoReq struct { + // 群 ID + Id int64 `json:"id"` + // 如果同时填了 idStr, 则优先选择 idStr + IdStr string `json:"idStr"` + PersonId string `json:"-"` +} + +type GetGroupPubInfoResp struct { + *GroupInfo +} + +type GetGroupInfoByConditionReq struct { + // 查询方法 0:groupMarkId, 1:groupId + Tp int32 `json:"tp" binding:"oneof=0 1"` + Query string `json:"query" binding:"required"` + PersonId string `json:"-"` +} + +type GetGroupInfoByConditionResp struct { + Groups []*GroupInfo `json:"groups"` +} + +// ---群审批------------------ + +// GroupApplyInfo 群审批信息 +type GroupApplyInfo struct { + // 审批 ID + ApplyId string `json:"applyId,omitempty"` + // 群 ID + GroupId string `json:"id,omitempty"` + // 邀请人 ID, 空表示是自己主动申请的 + InviterId string `json:"inviterId,omitempty"` + // 申请加入人 ID + MemberId string `json:"memberId,omitempty"` + // 申请备注 + ApplyNote string `json:"applyNote,omitempty"` + // 审批人 ID + OperatorId string `json:"operatorId,omitempty"` + // 审批情况 0=待审批, 1=审批通过, 2=审批不通过, 10=审批忽略 + ApplyStatus int32 `json:"applyStatus,omitempty"` + // 拒绝原因 + RejectReason string `json:"rejectReason,omitempty"` + // 创建时间 ms + CreateTime int64 `json:"createTime,omitempty"` + // 修改时间 ms + UpdateTime int64 `json:"updateTime,omitempty"` +} + +// CreateGroupApplyReq 创建群审批请求 +type CreateGroupApplyReq struct { + // 群 ID + Id string `json:"id,omitempty" binding:"required"` + // 申请备注 + ApplyNote string `json:"applyNote,omitempty"` + + PersonId string `json:"-"` +} + +// CreateGroupApplyResp 创建群审批响应 +type CreateGroupApplyResp struct { +} + +// GetGroupApplyByIdReq 查询群审批请求 +type GetGroupApplyByIdReq struct { + // 审批 ID + ApplyId string `json:"applyId" binding:"required"` + // 群 ID + Id string `json:"id" binding:"required"` + + PersonId string `json:"-"` +} + +// GetGroupApplysReq 查询群审批列表请求 +type GetGroupApplysReq struct { + // 群 ID + Id string `json:"id" binding:"required"` + // 每页记录数 + Count int32 `json:"count" binding:"required"` + // 当前审批记录数量 + Offset int32 `json:"offset"` + + PersonId string `json:"-"` +} + +// GetGroupApplysResp 查询群审批响应 +type GetGroupApplysResp struct { + GroupApplys []*GroupApplyInfo `json:"applys"` +} + +// AcceptGroupApplyReq 接受加群审批请求 +type AcceptGroupApplyReq struct { + // 审批 ID + ApplyId string `json:"applyId" binding:"required"` + // 群 ID + Id string `json:"id" binding:"required"` + + PersonId string `json:"-"` +} + +type AcceptGroupApplyResp struct { +} + +// RejectGroupApplyReq 拒绝加群审批请求 +type RejectGroupApplyReq struct { + // 审批 ID + ApplyId string `json:"applyId" binding:"required"` + // 群 ID + Id string `json:"id" binding:"required"` + // 拒绝原因 + RejectReason string `json:"rejectReason"` + + PersonId string `json:"-"` +} + +type RejectGroupApplyResp struct { +} diff --git a/gateway/api/v1/internal/types/types.go b/gateway/api/v1/internal/types/types.go new file mode 100644 index 0000000..70fcae9 --- /dev/null +++ b/gateway/api/v1/internal/types/types.go @@ -0,0 +1,6 @@ +// Code generated by goctl. DO NOT EDIT. +package types + +type Hello struct { + Content string `json:"content"` +} diff --git a/gateway/api/v1/version.go b/gateway/api/v1/version.go new file mode 100644 index 0000000..1195a0a --- /dev/null +++ b/gateway/api/v1/version.go @@ -0,0 +1,16 @@ +package main + +//var ( +// // The full version string +// Version = "0.0.7" +// +// // GitCommit is set with --ldflags "-X main.gitCommit=$(git rev-parse --short=8 HEAD)" +// GitCommit string +//) +// +//func GetVersion() string { +// if GitCommit != "" { +// return Version + "-" + GitCommit +// } +// return Version +//} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..f1de7aa --- /dev/null +++ b/go.mod @@ -0,0 +1,64 @@ +module gitlab.33.cn/chat/dtalk + +go 1.15 + +require ( + github.com/33cn/chain33 v1.65.3 + github.com/BurntSushi/toml v0.3.1 + github.com/Shopify/sarama v1.19.0 + github.com/Terry-Mao/goim v0.0.0-20210523140626-e742c99ad76e + github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 + github.com/aliyun/alibaba-cloud-sdk-go v1.61.948 + github.com/aliyun/aliyun-oss-go-sdk v2.1.8+incompatible + github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect + github.com/bsm/sarama-cluster v2.1.15+incompatible + github.com/dgrijalva/jwt-go v3.2.0+incompatible + github.com/gammazero/workerpool v1.1.2 + github.com/gin-gonic/gin v1.6.3 + github.com/go-sql-driver/mysql v1.5.0 + github.com/gogo/protobuf v1.3.2 + github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b + github.com/golang/protobuf v1.5.2 + github.com/gomodule/redigo v2.0.0+incompatible + github.com/google/uuid v1.3.0 + github.com/haltingstate/secp256k1-go v0.0.0-20151224084235-572209b26df6 + github.com/huaweicloud/huaweicloud-sdk-go-obs v3.21.1+incompatible + github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.0.35-rc + github.com/inconshreveable/log15 v0.0.0-20201112154412-8562bdadbbac + github.com/jinzhu/gorm v1.9.16 + github.com/mailru/easyjson v0.7.7 // indirect + github.com/minio/minio-go/v7 v7.0.12 + github.com/oofpgDLD/u-push v0.0.2 + github.com/opentracing/opentracing-go v1.2.0 + github.com/pkg/errors v0.9.1 + github.com/prometheus/client_golang v1.11.0 + github.com/rs/xid v1.3.0 + github.com/rs/zerolog v1.21.0 + github.com/spf13/cobra v1.0.0 + github.com/stretchr/testify v1.7.0 + github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14 + github.com/swaggo/gin-swagger v1.1.0 + github.com/swaggo/swag v1.7.8 + github.com/tencentyun/tls-sig-api-v2-golang v1.2.0 + github.com/uber/jaeger-client-go v2.28.0+incompatible + github.com/ugorji/go v1.1.13 // indirect + gitlab.33.cn/chat/im v1.0.10 + //gitlab.33.cn/chat/imparse v0.0.0-00010101000000-000000000000 + //gitlab.33.cn/chat/im-pkg v0.0.0-00010101000000-000000000000 + gitlab.33.cn/chat/im-pkg v0.0.1 + gitlab.33.cn/chat/imparse v1.0.6 + gitlab.33.cn/utils/go-kit v1.0.7 + go.etcd.io/etcd/api/v3 v3.5.0 + go.etcd.io/etcd/client/v3 v3.5.0 + golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 + golang.org/x/net v0.0.0-20211020060615-d418f374d309 // indirect + golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359 // indirect + google.golang.org/grpc v1.40.0 + google.golang.org/protobuf v1.27.1 + gopkg.in/Shopify/sarama.v1 v1.19.0 + gopkg.in/go-playground/validator.v8 v8.18.2 + gopkg.in/yaml.v2 v2.4.0 +) + +//replace gitlab.33.cn/chat/imparse => ../imparse +//replace gitlab.33.cn/chat/im-pkg => ../im-pkg diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..0aafa8f --- /dev/null +++ b/go.sum @@ -0,0 +1,2398 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/bigtable v1.2.0/go.mod h1:JcVAOl45lrTmQfLj7T6TxyMzIN/3FGGcFm+2xVAli2o= +cloud.google.com/go/bigtable v1.3.0/go.mod h1:z5EyKrPE8OQmeg4h5MNdKvuSnI9CCT49Ki3f23aBzio= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE= +dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= +dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= +dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= +git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= +github.com/33cn/chain33 v0.0.0-20200527072033-e43d8da29c46/go.mod h1:RJsUKcMdXtCgpqp1W1ga6jTCieuJm6n7qd3XmnPXwa4= +github.com/33cn/chain33 v0.0.0-20200605043414-355d96f9ec97/go.mod h1:RJsUKcMdXtCgpqp1W1ga6jTCieuJm6n7qd3XmnPXwa4= +github.com/33cn/chain33 v1.64.0/go.mod h1:RJsUKcMdXtCgpqp1W1ga6jTCieuJm6n7qd3XmnPXwa4= +github.com/33cn/chain33 v1.65.3 h1:EeLQN+iYTxTtndtADVzWMWzzXUdCNQRjBqD9RnU0DPI= +github.com/33cn/chain33 v1.65.3/go.mod h1:27Z1b54wJ6EhE1IGA3S5AxSQ/t8dktFVgPnkLNbMFoE= +github.com/33cn/plugin v1.64.1-0.20200529022142-4c8918d6741c/go.mod h1:PKdkkj3I3NoPh2ahQa0cZ4l94PCtG34Bxm8YzaUq1rs= +github.com/360EntSecGroup-Skylar/excelize v1.4.1/go.mod h1:vnax29X2usfl7HHkBrX5EvSCJcmH3dT9luvxzu8iGAE= +github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= +github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= +github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M= +github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= +github.com/Azure/azure-sdk-for-go v41.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= +github.com/Azure/go-autorest/autorest v0.10.0/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630= +github.com/Azure/go-autorest/autorest v0.11.9/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw= +github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= +github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= +github.com/Azure/go-autorest/autorest/adal v0.8.3/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= +github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= +github.com/Azure/go-autorest/autorest/azure/auth v0.5.3/go.mod h1:4bJZhUhcq8LB20TruwHbAQsmUs2Xh+QR7utuJpLXX3A= +github.com/Azure/go-autorest/autorest/azure/cli v0.4.2/go.mod h1:7qkJkT+j6b+hIpzMOwPChJhTqS8VbsqqgULzMNRugoM= +github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= +github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/to v0.3.0/go.mod h1:MgwOyqaIuKdG4TL/2ywSsIWKAfJfgHDo8ObuUk3t5sA= +github.com/Azure/go-autorest/autorest/validation v0.2.0/go.mod h1:3EEqHnBxQGHXRYq3HT1WyXAvT7LLY3tl70hw6tQIbjI= +github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= +github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= +github.com/BurntSushi/toml v0.0.0-20170626110600-a368813c5e64/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/ClickHouse/clickhouse-go v1.4.3/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI= +github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/DATA-DOG/go-sqlmock v1.4.1/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= +github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM= +github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETFUpAzWW2ep1Y= +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/sprig v2.16.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/NebulousLabs/Sia v1.3.7/go.mod h1:SCASk6mV8QdEojKyecjj/Jd0OGSXkZonkhow7XXKk6Q= +github.com/NebulousLabs/entropy-mnemonics v0.0.0-20170316012907-7b01a644a636/go.mod h1:ed2ZsnmJfqVNZOwxWWFZaSHJY3ifOjCS7i5yX9dvKHs= +github.com/NebulousLabs/errors v0.0.0-20171229012116-7ead97ef90b8/go.mod h1:J7tUI9Fg4YuFLsqeLE5uIp93Fot9oBCw2vwZJvLmWso= +github.com/NebulousLabs/fastrand v0.0.0-20180208210444-3cf7173006a0/go.mod h1:Bdzq+51GR4/0DIhaICZEOm+OHvXGwwB2trKZ8B4Y6eQ= +github.com/NebulousLabs/merkletree v0.0.0-20181025040823-2a1d1d1dc33c/go.mod h1:Cn056wBLKay+uIS9LJn7ymwhgC5mqbOtG6iOhEvyy4M= +github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= +github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/SAP/go-hdb v0.14.1/go.mod h1:7fdQLVC2lER3urZLjZCm0AuMQfApof92n3aylBPEkMo= +github.com/Shopify/sarama v1.19.0 h1:9oksLxC6uxVPHPVYUmq6xhr1BOF/hHobWH2UzO67z1s= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/Terry-Mao/goim v0.0.0-20201205074412-7c5316e1c2e5/go.mod h1:TbixifnJSmnDOkbQHYrXgQ04vqlSq6zlVQ5BWvadH44= +github.com/Terry-Mao/goim v0.0.0-20210523140626-e742c99ad76e h1:Z6Gwt8yTX9lSM0+L2SleFjzepfWrG6YD3OHfcLVnVgg= +github.com/Terry-Mao/goim v0.0.0-20210523140626-e742c99ad76e/go.mod h1:5fnMBvt4HNGOlPpQINxK2CBYkU0zuaiNFRTUKwSM5/4= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/XiaoMi/pegasus-go-client v0.0.0-20181029071519-9400942c5d1c/go.mod h1:KcL6D/4RZ8RAYzQ5gKI0odcdWUmCVlbQTOlWrhP71CY= +github.com/XiaoMi/pegasus-go-client v0.0.0-20210825081735-b8a75c1eac2b h1:KF7k0g1S53oeveZxGM2wfyT5PSpO82ZxBrYlA5mM0cw= +github.com/XiaoMi/pegasus-go-client v0.0.0-20210825081735-b8a75c1eac2b/go.mod h1:VrfgKISflRhFm32m3e0SXLccvNJTyG8PRywWbUuGEfY= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/agiledragon/gomonkey v2.0.2+incompatible h1:eXKi9/piiC3cjJD1658mEE2o3NjkJ5vDLgYjCQu0Xlw= +github.com/agiledragon/gomonkey v2.0.2+incompatible/go.mod h1:2NGfXu1a80LLr2cmWXGBDaHEjb1idR6+FVlX5T3D9hw= +github.com/agiledragon/gomonkey/v2 v2.3.1 h1:k+UnUY0EMNYUFUAQVETGY9uUTxjMdnUkP0ARyJS1zzs= +github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY= +github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0= +github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= +github.com/alicebob/miniredis/v2 v2.14.1/go.mod h1:uS970Sw5Gs9/iK3yBg0l9Uj9s25wXxSpQUE9EaJ/Blg= +github.com/aliyun/alibaba-cloud-sdk-go v1.61.948 h1:bOJmb16gFjMxdvUdwISk5PSc9zVTqo9ZABQBRs+aGJY= +github.com/aliyun/alibaba-cloud-sdk-go v1.61.948/go.mod h1:pUKYbK5JQ+1Dfxk80P0qxGqe5dkxDoabbZS7zOcouyA= +github.com/aliyun/aliyun-oss-go-sdk v2.1.8+incompatible h1:hLUNPbx10wawWW7DeNExvTrlb90db3UnnNTFKHZEFhE= +github.com/aliyun/aliyun-oss-go-sdk v2.1.8+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/antlr/antlr4 v0.0.0-20210105212045-464bcbc32de2/go.mod h1:T7PbCXFs94rrTttyxjbyT5+/1V8T2TYDejxUfHJjw1Y= +github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ= +github.com/apache/arrow/go/arrow v0.0.0-20191024131854-af6fa24be0db/go.mod h1:VTxUBvSJ3s3eHAg65PNgrsn5BtqCRPdmyXh6rAfdxN0= +github.com/apache/arrow/go/arrow v0.0.0-20200601151325-b2287a20f230/go.mod h1:QNYViu/X0HXDHw7m3KXzWSVXIbfUvJqBFe6Gj8/pYA0= +github.com/apache/arrow/go/arrow v0.0.0-20200923215132-ac86123a3f01/go.mod h1:QNYViu/X0HXDHw7m3KXzWSVXIbfUvJqBFe6Gj8/pYA0= +github.com/apache/thrift v0.0.0-20171203172758-327ebb6c2b6d/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-metrics v0.3.3/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.29.16/go.mod h1:1KvfttTE3SPKMpo8g2c6jL3ZKfXtFvKscTgahTma5Xg= +github.com/aws/aws-sdk-go v1.30.12/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f h1:ZNv7On9kyUzm7fvRZumSyy/IUiSC7AzL0I1jKKtwooA= +github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc= +github.com/benbjohnson/clock v1.0.2/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= +github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/benbjohnson/immutable v0.2.1/go.mod h1:uc6OHo6PN2++n98KHLxW8ef4W42ylHiQSENghE1ezxI= +github.com/benbjohnson/tmpl v1.0.0/go.mod h1:igT620JFIi44B6awvU9IsDhR77IXWtFigTLil/RPdps= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bilibili/discovery v1.0.1/go.mod h1:daS5nEYEBt0scrrmuoNCxWXDHFK6gtEpjhVKG6MUxUg= +github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4= +github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= +github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= +github.com/bonitoo-io/go-sql-bigquery v0.3.4-1.4.0/go.mod h1:J4Y6YJm0qTWB9aFziB7cPeSyc6dOZFyJdteSeybVpXQ= +github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= +github.com/bsm/sarama-cluster v2.1.15+incompatible h1:RkV6WiNRnqEEbp81druK8zYhmnIgdOjqSVi0+9Cnl2A= +github.com/bsm/sarama-cluster v2.1.15+incompatible/go.mod h1:r7ao+4tTNXvWm+VRpRJchr2kQhqxgmAp2iEX5W96gMM= +github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32/go.mod h1:DrZx5ec/dmnfpw9KyYoQyYo7d0KEvTkk/5M/vbZjAr8= +github.com/btcsuite/btcd v0.0.0-20190523000118-16327141da8c/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= +github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btcd v0.21.0-beta/go.mod h1:ZSWyehm27aAuS9bvkATT+Xte3hjHZ+MRgMY/8NJ7K94= +github.com/btcsuite/btcd v0.22.0-beta h1:LTDpDKUM5EeOFBPM8IXpinEcmZ6FWfNZbE3lfrfdnWo= +github.com/btcsuite/btcd v0.22.0-beta/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190207003914-4c204d697803/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= +github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce h1:YtWJF7RHm2pYCvA5t0RPmAaLUhREsKuKd+SLhxFbFeQ= +github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= +github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= +github.com/cactus/go-statsd-client/statsd v0.0.0-20191106001114-12b4e2b38748/go.mod h1:l/bIBLeOl9eX+wxJAzxS4TveKRtAqlyDpHjhkfO0MEI= +github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cenkalti/backoff v0.0.0-20181003080854-62661b46c409/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/cenkalti/backoff/v4 v4.1.0/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/cenkalti/backoff/v4 v4.1.1 h1:G2HAfAmvm/GcKan2oOQpBXOd2tT2G57ZnZGWa1PxPBQ= +github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM= +github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE= +github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/coreos/bbolt v1.3.0/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.15+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.25+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f h1:JOrtw2xFKzlg+cbHpyrpLDmnN1HqhBfnX7WDiW7eG2c= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= +github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= +github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= +github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018/go.mod h1:rQYf4tfk5sSwFsnDg3qYaBxSjsD9S8+59vW0dKUgme4= +github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR6AkioZ1ySsx5yxlDQZ8stG2b88gTPxgJU= +github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U= +github.com/dchest/blake256 v1.0.0/go.mod h1:xXNWCE1jsAP8DAjP+rKw2MbeqLczjI3TRx2VK+9OEYY= +github.com/decred/base58 v1.0.0/go.mod h1:LLY1p5e3g91byL/UO1eiZaYd+uRoVRarybgcoymu9Ks= +github.com/decred/base58 v1.0.3 h1:KGZuh8d1WEMIrK0leQRM47W85KqCAdl2N+uagbctdDI= +github.com/decred/base58 v1.0.3/go.mod h1:pXP9cXCfM2sFLb2viz2FNIdeMWmZDBKG3ZBYbiSM78E= +github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= +github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M= +github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/denisenkom/go-mssqldb v0.10.0 h1:QykgLZBorFE95+gO3u9esLd0BmbvpWp0/waNNZfHBM8= +github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/dgraph-io/badger v1.5.5-0.20190226225317-8115aed38f8f/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ= +github.com/dgraph-io/badger v1.6.0-rc1/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= +github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= +github.com/dgraph-io/badger v1.6.1/go.mod h1:FRmFw3uxvcpa8zG3Rxs0th+hCLIuaQg8HlNV5bjgnuU= +github.com/dgraph-io/badger v1.6.2 h1:mNw0qs90GVgGGWylh0umH5iag1j6n/PeJtNvL6KY/x8= +github.com/dgraph-io/badger v1.6.2/go.mod h1:JW2yswe3V058sS0kZ2h/AXeDSqFjxnZcRrVH//y2UQE= +github.com/dgraph-io/ristretto v0.0.2 h1:a5WaUrDa0qm0YrAAS1tUykT5El3kt62KNZZeMxQn3po= +github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-bitstream v0.0.0-20180413035011-3522498ce2c8/go.mod h1:VMaSuZ+SZcx/wljOQKvp5srsbCiKDEb6K2wC4+PiBmQ= +github.com/dgryski/go-farm v0.0.0-20190104051053-3adb47b1fb0f/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/dgryski/go-sip13 v0.0.0-20190329191031-25c5027a8c7b/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= +github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eapache/go-resiliency v1.1.0 h1:1NtRmCAqadE2FN4ZcN6g90TP3uk8cg9rn9eNK2197aU= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-resiliency v1.2.0 h1:v7g92e/KSN71Rq7vSThKaWIq68fL4YHvWyiUKorFR1Q= +github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/proto v1.9.0/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= +github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/flynn/noise v1.0.0 h1:DlTHqmzmvcEiKj+4RYo/imoswx/4r6iBlCMfVtrMXpQ= +github.com/flynn/noise v1.0.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/foxcpp/go-mockdns v0.0.0-20201212160233-ede2f9158d15/go.mod h1:tPg4cp4nseejPd+UKxtCVQ2hUxNTZ7qQZJa7CLriIeo= +github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= +github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= +github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o= +github.com/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebPhedY= +github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= +github.com/frankban/quicktest v1.14.0 h1:+cqqvzZV87b4adx/5ayVOaYZ2CrvM4ejQvUdBzPPUss= +github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/gammazero/deque v0.1.0 h1:f9LnNmq66VDeuAlSAapemq/U7hJ2jpIWa4c09q8Dlik= +github.com/gammazero/deque v0.1.0/go.mod h1:KQw7vFau1hHuM8xmI9RbgKFbAsQFWmBpqQ2KenFLk6M= +github.com/gammazero/workerpool v1.1.2 h1:vuioDQbgrz4HoaCi2q1HLlOXdpbap5AET7xu5/qj87g= +github.com/gammazero/workerpool v1.1.2/go.mod h1:UelbXcO0zCIGFcufcirHhq2/xtLXJdQ29qZNlXG9OjQ= +github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/pprof v1.3.0 h1:G9eK6HnbkSqDZBYbzG4wrjCsA4e+cvYAHUZw6W+W9K0= +github.com/gin-contrib/pprof v1.3.0/go.mod h1:waMjT1H9b179t3CxuG1cV3DHpga6ybizwfBaM5OXaB0= +github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v0.0.0-20180512030042-bf7803815b0b/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y= +github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y= +github.com/gin-gonic/gin v1.6.2/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= +github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= +github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= +github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= +github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= +github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= +github.com/go-chi/chi v4.1.0+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= +github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= +github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= +github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= +github.com/go-openapi/analysis v0.19.4/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= +github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU= +github.com/go-openapi/analysis v0.19.10/go.mod h1:qmhS3VNFxBlquFJ0RGoDtylO9y4pgTAUNE9AEEMdlJQ= +github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= +github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= +github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= +github.com/go-openapi/errors v0.19.3/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= +github.com/go-openapi/errors v0.19.4/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= +github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= +github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= +github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs= +github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= +github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs= +github.com/go-openapi/loads v0.19.3/go.mod h1:YVfqhUCdahYwR3f3iiwQLhicVRvLlU/WO5WPaZvcvSI= +github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk= +github.com/go-openapi/loads v0.19.5/go.mod h1:dswLCAdonkRufe/gSUC3gN8nTSaB9uaS2es0x5/IbjY= +github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= +github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= +github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= +github.com/go-openapi/runtime v0.19.15/go.mod h1:dhGWCTKRXlAfGnQG0ONViOZpjfg0m2gUt9nTQPQZuoo= +github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= +github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= +github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/spec v0.19.6/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= +github.com/go-openapi/spec v0.19.7/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= +github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M= +github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= +github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= +github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= +github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= +github.com/go-openapi/strfmt v0.19.2/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= +github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= +github.com/go-openapi/strfmt v0.19.4/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= +github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= +github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= +github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= +github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= +github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.7/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= +github.com/go-openapi/swag v0.19.9/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= +github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= +github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= +github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= +github.com/go-openapi/validate v0.19.3/go.mod h1:90Vh6jjkTn+OT1Eefm0ZixWNFjhtOH7vS9k0lo6zwJo= +github.com/go-openapi/validate v0.19.8/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= +github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= +github.com/go-playground/validator/v10 v10.6.1 h1:W6TRDXt4WcWp4c4nf/G+6BkGdhiIo0k417gfr+V6u4I= +github.com/go-playground/validator/v10 v10.6.1/go.mod h1:xm76BBt941f7yWdGnI2DVPFFg1UK3YY04qifoXU3lOk= +github.com/go-redis/redis v6.15.7+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-xorm/builder v0.3.4/go.mod h1:KxkQkNN1DpPKTedxXyTQcmH+rXfvk4LZ9SOOBoZBAxw= +github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:56xuuqnHyryaerycW3BfssRdxQstACi0Epw/yC5E2xM= +github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= +github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= +github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= +github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= +github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= +github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= +github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= +github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= +github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= +github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= +github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= +github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= +github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= +github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= +github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= +github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= +github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.2.2-0.20190730201129-28a6bbf47e48/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= +github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3/go.mod h1:nPpo7qLxd6XL3hWJG/O60sR8ZKfMCiIoNap5GvD12KU= +github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.2-0.20190904063534-ff6b7dc882cf h1:gFVkHXmVAhEbxZVDln5V9GKrLaluNoFHDbrZwAWZgws= +github.com/golang/snappy v0.0.2-0.20190904063534-ff6b7dc882cf/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= +github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0= +github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/flatbuffers v2.0.0+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gopacket v1.1.17/go.mod h1:UdDNZ1OO62aGYVnPhxT1U6aI7ukYtA/kB8vaU0diBUM= +github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= +github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200417002340-c6e0a841f49a/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= +github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/googleapis/gnostic v0.4.0/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU= +github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= +github.com/gophercloud/gophercloud v0.10.0/go.mod h1:gmC5oQqMDOMO1t1gq5DquX/yAU808e/4mzjjDA76+Ss= +github.com/gopherjs/gopherjs v0.0.0-20180424202546-8dffc02ea1cb/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.14.3/go.mod h1:6CwZWGDSPRJidgKAtJVvND6soZe6fT7iteq8wDPdhb0= +github.com/grpc-ecosystem/grpc-gateway v1.14.4/go.mod h1:6CwZWGDSPRJidgKAtJVvND6soZe6fT7iteq8wDPdhb0= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/gxed/hashland/keccakpg v0.0.1/go.mod h1:kRzw3HkwxFU1mpmPP8v1WyQzwdGfmKFJ6tItnhQ67kU= +github.com/gxed/hashland/murmur3 v0.0.1/go.mod h1:KjXop02n4/ckmZSnY2+HKcLud/tcmvhST0bie/0lS48= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= +github.com/haltingstate/secp256k1-go v0.0.0-20151224084235-572209b26df6 h1:HE4YDtvtpZgjRJ2tCOmaXlcpBTFG2e0jvfNntM5sXOs= +github.com/haltingstate/secp256k1-go v0.0.0-20151224084235-572209b26df6/go.mod h1:73mKQiY8bLnscfGakn57WAJZTzT0eSUAy3qgMQNR/DI= +github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= +github.com/hashicorp/consul/api v1.4.0/go.mod h1:xc8u05kyMa3Wjr9eEAsIAo3dg8+LywT5E/Cl7cNS5nU= +github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/consul/sdk v0.4.0/go.mod h1:fY08Y9z5SvJqevyZNy6WWPXiG3KwBPAvlcdx16zZ0fM= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v0.12.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.2.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/memberlist v0.1.4/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/memberlist v0.2.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hashicorp/serf v0.9.0/go.mod h1:YL0HO+FifKOW2u1ke99DGVu1zhcpZzNwrLIqBC7vbYU= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo= +github.com/huaweicloud/huaweicloud-sdk-go-obs v3.21.1+incompatible h1:EFjtiulITiEktaZrr0OPlymTmrlpvSAa/xvv08kTQEU= +github.com/huaweicloud/huaweicloud-sdk-go-obs v3.21.1+incompatible/go.mod h1:l7VUhRbTKCzdOacdT4oWCwATKyvZqUOlOqr0Ous3k4s= +github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.0.35-rc h1:Gj1YcwhIxwJr8M9ioEWNYVNVlpbs50vrEN/XVuiMuTI= +github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.0.35-rc/go.mod h1:Pp3sd2tx3j9qC7Ij6jGh5phZwTrI+/HUBK90f3Cn2CI= +github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= +github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= +github.com/huin/goupnp v1.0.2 h1:RfGLP+h3mvisuWEyybxNq5Eft3NWhHLPeUN72kpKZoI= +github.com/huin/goupnp v1.0.2/go.mod h1:0dxJBVBHqTMjIUMkESDTNgOOx/Mw5wYIfyFmdzSamkM= +github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= +github.com/iancoleman/strcase v0.1.2/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.4/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/inconshreveable/log15 v0.0.0-20200109203555-b30bc20e4fd1/go.mod h1:cOaXtrgN4ScfRrD9Bre7U1thNq5RtJ8ZoP4iXVGRj6o= +github.com/inconshreveable/log15 v0.0.0-20201112154412-8562bdadbbac h1:n1DqxAo4oWPMvH1+v+DLYlMCecgumhhgnxAPdqDIFHI= +github.com/inconshreveable/log15 v0.0.0-20201112154412-8562bdadbbac/go.mod h1:cOaXtrgN4ScfRrD9Bre7U1thNq5RtJ8ZoP4iXVGRj6o= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/flux v0.65.0/go.mod h1:BwN2XG2lMszOoquQaFdPET8FRQfrXiZsWmcMO9rkaVY= +github.com/influxdata/flux v0.131.0/go.mod h1:CKvnYe6FHpTj/E0YGI7TcOZdGiYHoToOPSnoa12RtKI= +github.com/influxdata/httprouter v1.3.1-0.20191122104820-ee83e2772f69/go.mod h1:pwymjR6SrP3gD3pRj9RJwdl1j5s3doEEV8gS4X9qSzA= +github.com/influxdata/influxdb v1.7.9/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= +github.com/influxdata/influxdb v1.8.0/go.mod h1:SIzcnsjaHRFpmlxpJ4S3NT64qtEKYweNTUMb/vh0OMQ= +github.com/influxdata/influxdb v1.9.5/go.mod h1:4uPVvcry9KWQVWLxyT9641qpkRXUBN+xa0MJFFNNLKo= +github.com/influxdata/influxdb-client-go/v2 v2.3.1-0.20210518120617-5d1fff431040/go.mod h1:vLNHdxTJkIf2mSLvGrpj8TCcISApPoXkaxP8g9uRlW8= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/influxdata/influxql v1.1.0/go.mod h1:KpVI7okXjK6PRi3Z5B+mtKZli+R1DnZgb3N+tzevNgo= +github.com/influxdata/influxql v1.1.1-0.20210223160523-b6ab99450c93/go.mod h1:gHp9y86a/pxhjJ+zMjNXiQAA197Xk9wLxaz+fGG+kWk= +github.com/influxdata/line-protocol v0.0.0-20180522152040-32c6aa80de5e/go.mod h1:4kt73NQhadE3daL3WhR5EJ/J2ocX0PZzwxQ0gXJ7oFE= +github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= +github.com/influxdata/pkg-config v0.2.8/go.mod h1:EMS7Ll0S4qkzDk53XS3Z72/egBsPInt+BeRxb0WeSwk= +github.com/influxdata/promql/v2 v2.12.0/go.mod h1:fxOPu+DY0bqCTCECchSRtWfc+0X19ybifQhZoQNF5D8= +github.com/influxdata/roaring v0.4.13-0.20180809181101-fc520f41fab6/go.mod h1:bSgUQ7q5ZLSO+bKBGqJiCBGAl+9DxyW63zLTujjUlOE= +github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9/go.mod h1:Js0mqiSBE6Ffsg94weZZ2c+v/ciT8QRHFOap7EKDrR0= +github.com/influxdata/tdigest v0.0.2-0.20210216194612-fc98d27c9e8b/go.mod h1:Z0kXnxzbTC2qrx4NaIzYkE1k66+6oEDQTvL95hQFh5Y= +github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1:Wbbw6tYNvwa5dlB6304Sd+82Z3f7PmVZHVKU637d4po= +github.com/ipfs/go-cid v0.0.1/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= +github.com/ipfs/go-cid v0.0.2/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= +github.com/ipfs/go-cid v0.0.3/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= +github.com/ipfs/go-cid v0.0.4/go.mod h1:4LLaPOQwmk5z9LBgQnpkivrx8BJjUyGwTXCd5Xfj6+M= +github.com/ipfs/go-cid v0.0.5/go.mod h1:plgt+Y5MnOey4vO4UlUazGqdbEXuFYitED67FexhXog= +github.com/ipfs/go-cid v0.0.6/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= +github.com/ipfs/go-cid v0.0.7 h1:ysQJVJA3fNDF1qigJbsSQOdjhVLsOEoPdh0+R97k3jY= +github.com/ipfs/go-cid v0.0.7/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= +github.com/ipfs/go-datastore v0.0.1/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE= +github.com/ipfs/go-datastore v0.1.0/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE= +github.com/ipfs/go-datastore v0.1.1/go.mod h1:w38XXW9kVFNp57Zj5knbKWM2T+KOZCGDRVNdgPHtbHw= +github.com/ipfs/go-datastore v0.4.0/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= +github.com/ipfs/go-datastore v0.4.1/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= +github.com/ipfs/go-datastore v0.4.4/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= +github.com/ipfs/go-datastore v0.4.5/go.mod h1:eXTcaaiN6uOlVCLS9GjJUJtlvJfM3xk23w3fyfrmmJs= +github.com/ipfs/go-datastore v0.4.6 h1:zU2cmweykxJ+ziXnA2cPtsLe8rdR/vrthOipLPuf6kc= +github.com/ipfs/go-datastore v0.4.6/go.mod h1:XSipLSc64rFKSFRFGo1ecQl+WhYce3K7frtpHkyPFUc= +github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk= +github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps= +github.com/ipfs/go-ds-badger v0.0.2/go.mod h1:Y3QpeSFWQf6MopLTiZD+VT6IC1yZqaGmjvRcKeSGij8= +github.com/ipfs/go-ds-badger v0.0.5/go.mod h1:g5AuuCGmr7efyzQhLL8MzwqcauPojGPUaHzfGTzuE3s= +github.com/ipfs/go-ds-badger v0.0.7/go.mod h1:qt0/fWzZDoPW6jpQeqUjR5kBfhDNB65jd9YlmAvpQBk= +github.com/ipfs/go-ds-badger v0.2.1/go.mod h1:Tx7l3aTph3FMFrRS838dcSJh+jjA7cX9DrGVwx/NOwE= +github.com/ipfs/go-ds-badger v0.2.3/go.mod h1:pEYw0rgg3FIrywKKnL+Snr+w/LjJZVMTBRn4FS6UHUk= +github.com/ipfs/go-ds-badger v0.2.7/go.mod h1:02rnztVKA4aZwDuaRPTf8mpqcKmXP7mLl6JPxd14JHA= +github.com/ipfs/go-ds-leveldb v0.0.1/go.mod h1:feO8V3kubwsEF22n0YRQCffeb79OOYIykR4L04tMOYc= +github.com/ipfs/go-ds-leveldb v0.1.0/go.mod h1:hqAW8y4bwX5LWcCtku2rFNX3vjDZCy5LZCg+cSZvYb8= +github.com/ipfs/go-ds-leveldb v0.4.1/go.mod h1:jpbku/YqBSsBc1qgME8BkWS4AxzF2cEu1Ii2r79Hh9s= +github.com/ipfs/go-ds-leveldb v0.4.2/go.mod h1:jpbku/YqBSsBc1qgME8BkWS4AxzF2cEu1Ii2r79Hh9s= +github.com/ipfs/go-ipfs-delay v0.0.0-20181109222059-70721b86a9a8/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw= +github.com/ipfs/go-ipfs-util v0.0.1/go.mod h1:spsl5z8KUnrve+73pOhSVZND1SIxPW5RyBCNzQxlJBc= +github.com/ipfs/go-ipfs-util v0.0.2 h1:59Sswnk1MFaiq+VcaknX7aYEyGyGDAA73ilhEK2POp8= +github.com/ipfs/go-ipfs-util v0.0.2/go.mod h1:CbPtkWJzjLdEcezDns2XYaehFVNXG9zrdrtMecczcsQ= +github.com/ipfs/go-ipns v0.1.2 h1:O/s/0ht+4Jl9+VoxoUo0zaHjnZUS+aBQIKTuzdZ/ucI= +github.com/ipfs/go-ipns v0.1.2/go.mod h1:ioQ0j02o6jdIVW+bmi18f4k2gRf0AV3kZ9KeHYHICnQ= +github.com/ipfs/go-log v0.0.1/go.mod h1:kL1d2/hzSpI0thNYjiKfjanbVNU+IIGA/WnNESY9leM= +github.com/ipfs/go-log v1.0.2/go.mod h1:1MNjMxe0u6xvJZgeqbJ8vdo2TKaGwZ1a0Bpza+sr2Sk= +github.com/ipfs/go-log v1.0.3/go.mod h1:OsLySYkwIbiSUR/yBTdv1qPtcE4FW3WPWk/ewz9Ru+A= +github.com/ipfs/go-log v1.0.4/go.mod h1:oDCg2FkjogeFOhqqb+N39l2RpTNPL6F/StPkB3kPgcs= +github.com/ipfs/go-log v1.0.5 h1:2dOuUCB1Z7uoczMWgAyDck5JLb72zHzrMnGnCNNbvY8= +github.com/ipfs/go-log v1.0.5/go.mod h1:j0b8ZoR+7+R99LD9jZ6+AJsrzkPbSXbZfGakb5JPtIo= +github.com/ipfs/go-log/v2 v2.0.2/go.mod h1:O7P1lJt27vWHhOwQmcFEvlmo49ry2VY2+JfBWFaa9+0= +github.com/ipfs/go-log/v2 v2.0.3/go.mod h1:O7P1lJt27vWHhOwQmcFEvlmo49ry2VY2+JfBWFaa9+0= +github.com/ipfs/go-log/v2 v2.0.5/go.mod h1:eZs4Xt4ZUJQFM3DlanGhy7TkwwawCZcSByscwkWG+dw= +github.com/ipfs/go-log/v2 v2.1.1/go.mod h1:2v2nsGfZsvvAJz13SyFzf9ObaqwHiHxsPLEHntrv9KM= +github.com/ipfs/go-log/v2 v2.1.3/go.mod h1:/8d0SH3Su5Ooc31QlL1WysJhvyOTDCjcCZ9Axpmri6g= +github.com/ipfs/go-log/v2 v2.3.0 h1:31Re/cPqFHpsRHgyVwjWADPoF0otB1WrjTy8ZFYwEZU= +github.com/ipfs/go-log/v2 v2.3.0/go.mod h1:QqGoj30OTpnKaG/LKTGTxoP2mmQtjVMEnK72gynbe/g= +github.com/ipfs/go-todocounter v0.0.1/go.mod h1:l5aErvQc8qKE2r7NDMjmq5UNAvuZy0rC8BHOplkWvZ4= +github.com/ipld/go-ipld-prime v0.9.0 h1:N2OjJMb+fhyFPwPnVvJcWU/NsumP8etal+d2v3G4eww= +github.com/ipld/go-ipld-prime v0.9.0/go.mod h1:KvBLMr4PX1gWptgkzRjVZCrLmSGcZCb/jioOQwCqZN8= +github.com/issue9/assert v1.0.0/go.mod h1:KLwR3U/5rbCxqwAnV3aCr+dz07aoIyIfk2lefIVr2BA= +github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA= +github.com/jackpal/go-nat-pmp v1.0.1/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= +github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= +github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= +github.com/jbenet/go-cienv v0.0.0-20150120210510-1bb1476777ec/go.mod h1:rGaEvXB4uRSZMmzKNLoXvTu1sfx+1kv/DojUlPrSZGs= +github.com/jbenet/go-cienv v0.1.0 h1:Vc/s0QbQtoxX8MwwSLWWh+xNNZvM3Lw7NsTcHrvvhMc= +github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA= +github.com/jbenet/go-temp-err-catcher v0.0.0-20150120210811-aac704a3f4f2/go.mod h1:8GXXJV31xl8whumTzdZsTt3RnUIiPqzkyf7mxToRCMs= +github.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABoLk/+KKHggpk= +github.com/jbenet/go-temp-err-catcher v0.1.0/go.mod h1:0kJRvmDZXNMIiJirNPEYfhpPwbGVtZVWC34vc5WLsDk= +github.com/jbenet/goprocess v0.0.0-20160826012719-b497e2f366b8/go.mod h1:Ly/wlsjFq/qrU3Rar62tu1gASgGw6chQbSh/XgIIXCY= +github.com/jbenet/goprocess v0.1.3/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= +github.com/jbenet/goprocess v0.1.4 h1:DRGOFReOMqqDNXwW70QkacFW0YN9QnwLV0Vqk+3oU0o= +github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= +github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o= +github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M= +github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/json-iterator/go v0.0.0-20180526014329-8744d7c5c7b4/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jsternberg/zap-logfmt v1.0.0/go.mod h1:uvPs/4X51zdkcm5jXl5SYoN+4RK21K8mysFmDaM/h+o= +github.com/jsternberg/zap-logfmt v1.2.0/go.mod h1:kz+1CUmCutPWABnNkOu9hOHKdT2q3TDYCcsFy9hpqb0= +github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/justinas/alice v1.2.0/go.mod h1:fN5HRH/reO/zrUflLfTN43t3vXvKzvZIENsNEe7i7qA= +github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0= +github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d/go.mod h1:P2viExyCEfeWGU259JnaQ34Inuec4R38JCyBx2edgD0= +github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= +github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= +github.com/kevinms/leakybucket-go v0.0.0-20200115003610-082473db97ca h1:qNtd6alRqd3qOdPrKXMZImV192ngQ0WSh1briEO33Tk= +github.com/kevinms/leakybucket-go v0.0.0-20200115003610-082473db97ca/go.mod h1:ph+C5vpnCcQvKBwJwKLTK3JLNGnBXYlG7m7JjoC/zYA= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.11.7 h1:0hzRabrMN4tSTvMfnL3SCv1ZGeAP23ynzodBgaHeMeg= +github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s= +github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4= +github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= +github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/koron/go-ssdp v0.0.0-20180514024734-4a0ed625a78b/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk= +github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk= +github.com/koron/go-ssdp v0.0.2 h1:fL3wAoyT6hXHQlORyXUW4Q23kkQpJRgEAYcZB5BR71o= +github.com/koron/go-ssdp v0.0.2/go.mod h1:XoLfkAiA2KeZsYh4DbHxD7h3nR2AZNqVQOa+LJuqPYs= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v0.0.0-20160406211939-eadb3ce320cb/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= +github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= +github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= +github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU= +github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/libp2p/go-addr-util v0.0.1/go.mod h1:4ac6O7n9rIAKB1dnd+s8IbbMXkt+oBpzX4/+RACcnlQ= +github.com/libp2p/go-addr-util v0.0.2/go.mod h1:Ecd6Fb3yIuLzq4bD7VcywcVSBtefcAwnUISBM3WG15E= +github.com/libp2p/go-addr-util v0.1.0 h1:acKsntI33w2bTU7tC9a0SaPimJGfSI0bFKC18ChxeVI= +github.com/libp2p/go-addr-util v0.1.0/go.mod h1:6I3ZYuFr2O/9D+SoyM0zEw0EF3YkldtTX406BpdQMqw= +github.com/libp2p/go-buffer-pool v0.0.1/go.mod h1:xtyIz9PMobb13WaxR6Zo1Pd1zXJKYg0a8KiIvDp3TzQ= +github.com/libp2p/go-buffer-pool v0.0.2 h1:QNK2iAFa8gjAe1SPz6mHSMuCcjs+X1wlHzeOSqcmlfs= +github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM= +github.com/libp2p/go-cidranger v1.1.0 h1:ewPN8EZ0dd1LSnrtuwd4709PXVcITVeuwbag38yPW7c= +github.com/libp2p/go-cidranger v1.1.0/go.mod h1:KWZTfSr+r9qEo9OkI9/SIEeAtw+NNoU0dXIXt15Okic= +github.com/libp2p/go-conn-security-multistream v0.1.0/go.mod h1:aw6eD7LOsHEX7+2hJkDxw1MteijaVcI+/eP2/x3J1xc= +github.com/libp2p/go-conn-security-multistream v0.2.0/go.mod h1:hZN4MjlNetKD3Rq5Jb/P5ohUnFLNzEAR4DLSzpn2QLU= +github.com/libp2p/go-conn-security-multistream v0.2.1 h1:ft6/POSK7F+vl/2qzegnHDaXFU0iWB4yVTYrioC6Zy0= +github.com/libp2p/go-conn-security-multistream v0.2.1/go.mod h1:cR1d8gA0Hr59Fj6NhaTpFhJZrjSYuNmhpT2r25zYR70= +github.com/libp2p/go-eventbus v0.0.2/go.mod h1:Hr/yGlwxA/stuLnpMiu82lpNKpvRy3EaJxPu40XYOwk= +github.com/libp2p/go-eventbus v0.1.0/go.mod h1:vROgu5cs5T7cv7POWlWxBaVLxfSegC5UGQf8A2eEmx4= +github.com/libp2p/go-eventbus v0.2.1 h1:VanAdErQnpTioN2TowqNcOijf6YwhuODe4pPKSDpxGc= +github.com/libp2p/go-eventbus v0.2.1/go.mod h1:jc2S4SoEVPP48H9Wpzm5aiGwUCBMfGhVhhBjyhhCJs8= +github.com/libp2p/go-flow-metrics v0.0.1/go.mod h1:Iv1GH0sG8DtYN3SVJ2eG221wMiNpZxBdp967ls1g+k8= +github.com/libp2p/go-flow-metrics v0.0.2/go.mod h1:HeoSNUrOJVK1jEpDqVEiUOIXqhbnS27omG0uWU5slZs= +github.com/libp2p/go-flow-metrics v0.0.3 h1:8tAs/hSdNvUiLgtlSy3mxwxWP4I9y/jlkPFT7epKdeM= +github.com/libp2p/go-flow-metrics v0.0.3/go.mod h1:HeoSNUrOJVK1jEpDqVEiUOIXqhbnS27omG0uWU5slZs= +github.com/libp2p/go-libp2p v0.3.1/go.mod h1:e6bwxbdYH1HqWTz8faTChKGR0BjPc8p+6SyP8GTTR7Y= +github.com/libp2p/go-libp2p v0.4.0/go.mod h1:9EsEIf9p2UDuwtPd0DwJsAl0qXVxgAnuDGRvHbfATfI= +github.com/libp2p/go-libp2p v0.6.1/go.mod h1:CTFnWXogryAHjXAKEbOf1OWY+VeAP3lDMZkfEI5sT54= +github.com/libp2p/go-libp2p v0.7.0/go.mod h1:hZJf8txWeCduQRDC/WSqBGMxaTHCOYHt2xSU1ivxn0k= +github.com/libp2p/go-libp2p v0.7.4/go.mod h1:oXsBlTLF1q7pxr+9w6lqzS1ILpyHsaBPniVO7zIHGMw= +github.com/libp2p/go-libp2p v0.8.1/go.mod h1:QRNH9pwdbEBpx5DTJYg+qxcVaDMAz3Ee/qDKwXujH5o= +github.com/libp2p/go-libp2p v0.14.4/go.mod h1:EIRU0Of4J5S8rkockZM7eJp2S0UrCyi55m2kJVru3rM= +github.com/libp2p/go-libp2p v0.15.0 h1:jbMbdmtizfpvl1+oQuGJzfGhttAtuxUCavF3enwFncg= +github.com/libp2p/go-libp2p v0.15.0/go.mod h1:8Ljmwon0cZZYKrOCjFeLwQEK8bqR42dOheUZ1kSKhP0= +github.com/libp2p/go-libp2p-asn-util v0.0.0-20200825225859-85005c6cf052 h1:BM7aaOF7RpmNn9+9g6uTjGJ0cTzWr5j9i9IKeun2M8U= +github.com/libp2p/go-libp2p-asn-util v0.0.0-20200825225859-85005c6cf052/go.mod h1:nRMRTab+kZuk0LnKZpxhOVH/ndsdr2Nr//Zltc/vwgo= +github.com/libp2p/go-libp2p-autonat v0.1.0/go.mod h1:1tLf2yXxiE/oKGtDwPYWTSYG3PtvYlJmg7NeVtPRqH8= +github.com/libp2p/go-libp2p-autonat v0.1.1/go.mod h1:OXqkeGOY2xJVWKAGV2inNF5aKN/djNA3fdpCWloIudE= +github.com/libp2p/go-libp2p-autonat v0.2.0/go.mod h1:DX+9teU4pEEoZUqR1PiMlqliONQdNbfzE1C718tcViI= +github.com/libp2p/go-libp2p-autonat v0.2.1/go.mod h1:MWtAhV5Ko1l6QBsHQNSuM6b1sRkXrpk0/LqCr+vCVxI= +github.com/libp2p/go-libp2p-autonat v0.2.2/go.mod h1:HsM62HkqZmHR2k1xgX34WuWDzk/nBwNHoeyyT4IWV6A= +github.com/libp2p/go-libp2p-autonat v0.4.2 h1:YMp7StMi2dof+baaxkbxaizXjY1RPvU71CXfxExzcUU= +github.com/libp2p/go-libp2p-autonat v0.4.2/go.mod h1:YxaJlpr81FhdOv3W3BTconZPfhaYivRdf53g+S2wobk= +github.com/libp2p/go-libp2p-blankhost v0.1.1/go.mod h1:pf2fvdLJPsC1FsVrNP3DUUvMzUts2dsLLBEpo1vW1ro= +github.com/libp2p/go-libp2p-blankhost v0.1.3/go.mod h1:KML1//wiKR8vuuJO0y3LUd1uLv+tlkGTAr3jC0S5cLg= +github.com/libp2p/go-libp2p-blankhost v0.1.4/go.mod h1:oJF0saYsAXQCSfDq254GMNmLNz6ZTHTOvtF4ZydUvwU= +github.com/libp2p/go-libp2p-blankhost v0.2.0 h1:3EsGAi0CBGcZ33GwRuXEYJLLPoVWyXJ1bcJzAJjINkk= +github.com/libp2p/go-libp2p-blankhost v0.2.0/go.mod h1:eduNKXGTioTuQAUcZ5epXi9vMl+t4d8ugUBRQ4SqaNQ= +github.com/libp2p/go-libp2p-circuit v0.1.1/go.mod h1:Ahq4cY3V9VJcHcn1SBXjr78AbFkZeIRmfunbA7pmFh8= +github.com/libp2p/go-libp2p-circuit v0.1.3/go.mod h1:Xqh2TjSy8DD5iV2cCOMzdynd6h8OTBGoV1AWbWor3qM= +github.com/libp2p/go-libp2p-circuit v0.1.4/go.mod h1:CY67BrEjKNDhdTk8UgBX1Y/H5c3xkAcs3gnksxY7osU= +github.com/libp2p/go-libp2p-circuit v0.2.1/go.mod h1:BXPwYDN5A8z4OEY9sOfr2DUQMLQvKt/6oku45YUmjIo= +github.com/libp2p/go-libp2p-circuit v0.4.0 h1:eqQ3sEYkGTtybWgr6JLqJY6QLtPWRErvFjFDfAOO1wc= +github.com/libp2p/go-libp2p-circuit v0.4.0/go.mod h1:t/ktoFIUzM6uLQ+o1G6NuBl2ANhBKN9Bc8jRIk31MoA= +github.com/libp2p/go-libp2p-connmgr v0.2.0/go.mod h1:C4r2FWWfbfk7U9NFcvY8oegIVxK3jPtOwFrEmtDunTg= +github.com/libp2p/go-libp2p-connmgr v0.2.4 h1:TMS0vc0TCBomtQJyWr7fYxcVYYhx+q/2gF++G5Jkl/w= +github.com/libp2p/go-libp2p-connmgr v0.2.4/go.mod h1:YV0b/RIm8NGPnnNWM7hG9Q38OeQiQfKhHCCs1++ufn0= +github.com/libp2p/go-libp2p-core v0.0.1/go.mod h1:g/VxnTZ/1ygHxH3dKok7Vno1VfpvGcGip57wjTU4fco= +github.com/libp2p/go-libp2p-core v0.0.4/go.mod h1:jyuCQP356gzfCFtRKyvAbNkyeuxb7OlyhWZ3nls5d2I= +github.com/libp2p/go-libp2p-core v0.0.6/go.mod h1:0d9xmaYAVY5qmbp/fcgxHT3ZJsLjYeYPMJAUKpaCHrE= +github.com/libp2p/go-libp2p-core v0.2.0/go.mod h1:X0eyB0Gy93v0DZtSYbEM7RnMChm9Uv3j7yRXjO77xSI= +github.com/libp2p/go-libp2p-core v0.2.2/go.mod h1:8fcwTbsG2B+lTgRJ1ICZtiM5GWCWZVoVrLaDRvIRng0= +github.com/libp2p/go-libp2p-core v0.2.3/go.mod h1:GqhyQqyIAPsxFYXHMjfXgMv03lxsvM0mFzuYA9Ib42A= +github.com/libp2p/go-libp2p-core v0.2.4/go.mod h1:STh4fdfa5vDYr0/SzYYeqnt+E6KfEV5VxfIrm0bcI0g= +github.com/libp2p/go-libp2p-core v0.2.5/go.mod h1:6+5zJmKhsf7yHn1RbmYDu08qDUpIUxGdqHuEZckmZOA= +github.com/libp2p/go-libp2p-core v0.3.0/go.mod h1:ACp3DmS3/N64c2jDzcV429ukDpicbL6+TrrxANBjPGw= +github.com/libp2p/go-libp2p-core v0.3.1/go.mod h1:thvWy0hvaSBhnVBaW37BvzgVV68OUhgJJLAa6almrII= +github.com/libp2p/go-libp2p-core v0.4.0/go.mod h1:49XGI+kc38oGVwqSBhDEwytaAxgZasHhFfQKibzTls0= +github.com/libp2p/go-libp2p-core v0.5.0/go.mod h1:49XGI+kc38oGVwqSBhDEwytaAxgZasHhFfQKibzTls0= +github.com/libp2p/go-libp2p-core v0.5.1/go.mod h1:uN7L2D4EvPCvzSH5SrhR72UWbnSGpt5/a35Sm4upn4Y= +github.com/libp2p/go-libp2p-core v0.5.3/go.mod h1:uN7L2D4EvPCvzSH5SrhR72UWbnSGpt5/a35Sm4upn4Y= +github.com/libp2p/go-libp2p-core v0.5.4/go.mod h1:uN7L2D4EvPCvzSH5SrhR72UWbnSGpt5/a35Sm4upn4Y= +github.com/libp2p/go-libp2p-core v0.5.5/go.mod h1:vj3awlOr9+GMZJFH9s4mpt9RHHgGqeHCopzbYKZdRjM= +github.com/libp2p/go-libp2p-core v0.5.6/go.mod h1:txwbVEhHEXikXn9gfC7/UDDw7rkxuX0bJvM49Ykaswo= +github.com/libp2p/go-libp2p-core v0.5.7/go.mod h1:txwbVEhHEXikXn9gfC7/UDDw7rkxuX0bJvM49Ykaswo= +github.com/libp2p/go-libp2p-core v0.6.0/go.mod h1:txwbVEhHEXikXn9gfC7/UDDw7rkxuX0bJvM49Ykaswo= +github.com/libp2p/go-libp2p-core v0.6.1/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= +github.com/libp2p/go-libp2p-core v0.7.0/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= +github.com/libp2p/go-libp2p-core v0.8.0/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= +github.com/libp2p/go-libp2p-core v0.8.1/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= +github.com/libp2p/go-libp2p-core v0.8.2/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= +github.com/libp2p/go-libp2p-core v0.8.5/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= +github.com/libp2p/go-libp2p-core v0.8.6/go.mod h1:dgHr0l0hIKfWpGpqAMbpo19pen9wJfdCGv51mTmdpmM= +github.com/libp2p/go-libp2p-core v0.9.0 h1:t97Mv0LIBZlP2FXVRNKKVzHJCIjbIWGxYptGId4+htU= +github.com/libp2p/go-libp2p-core v0.9.0/go.mod h1:ESsbz31oC3C1AvMJoGx26RTuCkNhmkSRCqZ0kQtJ2/8= +github.com/libp2p/go-libp2p-crypto v0.1.0/go.mod h1:sPUokVISZiy+nNuTTH/TY+leRSxnFj/2GLjtOTW90hI= +github.com/libp2p/go-libp2p-discovery v0.1.0/go.mod h1:4F/x+aldVHjHDHuX85x1zWoFTGElt8HnoDzwkFZm29g= +github.com/libp2p/go-libp2p-discovery v0.2.0/go.mod h1:s4VGaxYMbw4+4+tsoQTqh7wfxg97AEdo4GYBt6BadWg= +github.com/libp2p/go-libp2p-discovery v0.3.0/go.mod h1:o03drFnz9BVAZdzC/QUQ+NeQOu38Fu7LJGEOK2gQltw= +github.com/libp2p/go-libp2p-discovery v0.5.0/go.mod h1:+srtPIU9gDaBNu//UHvcdliKBIcr4SfDcm0/PfPJLug= +github.com/libp2p/go-libp2p-discovery v0.5.1 h1:CJylx+h2+4+s68GvrM4pGNyfNhOYviWBPtVv5PA7sfo= +github.com/libp2p/go-libp2p-discovery v0.5.1/go.mod h1:+srtPIU9gDaBNu//UHvcdliKBIcr4SfDcm0/PfPJLug= +github.com/libp2p/go-libp2p-kad-dht v0.2.1/go.mod h1:k7ONOlup7HKzQ68dE6lSnp07cdxdkmnRa+6B4Fh9/w0= +github.com/libp2p/go-libp2p-kad-dht v0.13.1 h1:wQgzOpoc+dcPVDb3h0HNWUjon5JiYEqsA4iNBUtIA7A= +github.com/libp2p/go-libp2p-kad-dht v0.13.1/go.mod h1:iVdxmsKHVPQSCGPP4V/A+tDFCLsxrREZUBX8ohOcKDw= +github.com/libp2p/go-libp2p-kbucket v0.2.1/go.mod h1:/Rtu8tqbJ4WQ2KTCOMJhggMukOLNLNPY1EtEWWLxUvc= +github.com/libp2p/go-libp2p-kbucket v0.3.1/go.mod h1:oyjT5O7tS9CQurok++ERgc46YLwEpuGoFq9ubvoUOio= +github.com/libp2p/go-libp2p-kbucket v0.4.7 h1:spZAcgxifvFZHBD8tErvppbnNiKA5uokDu3CV7axu70= +github.com/libp2p/go-libp2p-kbucket v0.4.7/go.mod h1:XyVo99AfQH0foSf176k4jY1xUJ2+jUJIZCSDm7r2YKk= +github.com/libp2p/go-libp2p-loggables v0.1.0/go.mod h1:EyumB2Y6PrYjr55Q3/tiJ/o3xoDasoRYM7nOzEpoa90= +github.com/libp2p/go-libp2p-mplex v0.2.0/go.mod h1:Ejl9IyjvXJ0T9iqUTE1jpYATQ9NM3g+OtR+EMMODbKo= +github.com/libp2p/go-libp2p-mplex v0.2.1/go.mod h1:SC99Rxs8Vuzrf/6WhmH41kNn13TiYdAWNYHrwImKLnE= +github.com/libp2p/go-libp2p-mplex v0.2.2/go.mod h1:74S9eum0tVQdAfFiKxAyKzNdSuLqw5oadDq7+L/FELo= +github.com/libp2p/go-libp2p-mplex v0.2.3/go.mod h1:CK3p2+9qH9x+7ER/gWWDYJ3QW5ZxWDkm+dVvjfuG3ek= +github.com/libp2p/go-libp2p-mplex v0.4.0/go.mod h1:yCyWJE2sc6TBTnFpjvLuEJgTSw/u+MamvzILKdX7asw= +github.com/libp2p/go-libp2p-mplex v0.4.1 h1:/pyhkP1nLwjG3OM+VuaNJkQT/Pqq73WzB3aDN3Fx1sc= +github.com/libp2p/go-libp2p-mplex v0.4.1/go.mod h1:cmy+3GfqfM1PceHTLL7zQzAAYaryDu6iPSC+CIb094g= +github.com/libp2p/go-libp2p-nat v0.0.4/go.mod h1:N9Js/zVtAXqaeT99cXgTV9e75KpnWCvVOiGzlcHmBbY= +github.com/libp2p/go-libp2p-nat v0.0.5/go.mod h1:1qubaE5bTZMJE+E/uu2URroMbzdubFz1ChgiN79yKPE= +github.com/libp2p/go-libp2p-nat v0.0.6 h1:wMWis3kYynCbHoyKLPBEMu4YRLltbm8Mk08HGSfvTkU= +github.com/libp2p/go-libp2p-nat v0.0.6/go.mod h1:iV59LVhB3IkFvS6S6sauVTSOrNEANnINbI/fkaLimiw= +github.com/libp2p/go-libp2p-netutil v0.1.0 h1:zscYDNVEcGxyUpMd0JReUZTrpMfia8PmLKcKF72EAMQ= +github.com/libp2p/go-libp2p-netutil v0.1.0/go.mod h1:3Qv/aDqtMLTUyQeundkKsA+YCThNdbQD54k3TqjpbFU= +github.com/libp2p/go-libp2p-noise v0.2.0/go.mod h1:IEbYhBBzGyvdLBoxxULL/SGbJARhUeqlO8lVSREYu2Q= +github.com/libp2p/go-libp2p-noise v0.2.2 h1:MRt5XGfYziDXIUy2udtMWfPmzZqUDYoC1FZoKnqPzwk= +github.com/libp2p/go-libp2p-noise v0.2.2/go.mod h1:IEbYhBBzGyvdLBoxxULL/SGbJARhUeqlO8lVSREYu2Q= +github.com/libp2p/go-libp2p-peer v0.2.0/go.mod h1:RCffaCvUyW2CJmG2gAWVqwePwW7JMgxjsHm7+J5kjWY= +github.com/libp2p/go-libp2p-peerstore v0.1.0/go.mod h1:2CeHkQsr8svp4fZ+Oi9ykN1HBb6u0MOvdJ7YIsmcwtY= +github.com/libp2p/go-libp2p-peerstore v0.1.3/go.mod h1:BJ9sHlm59/80oSkpWgr1MyY1ciXAXV397W6h1GH/uKI= +github.com/libp2p/go-libp2p-peerstore v0.1.4/go.mod h1:+4BDbDiiKf4PzpANZDAT+knVdLxvqh7hXOujessqdzs= +github.com/libp2p/go-libp2p-peerstore v0.2.0/go.mod h1:N2l3eVIeAitSg3Pi2ipSrJYnqhVnMNQZo9nkSCuAbnQ= +github.com/libp2p/go-libp2p-peerstore v0.2.1/go.mod h1:NQxhNjWxf1d4w6PihR8btWIRjwRLBr4TYKfNgrUkOPA= +github.com/libp2p/go-libp2p-peerstore v0.2.2/go.mod h1:NQxhNjWxf1d4w6PihR8btWIRjwRLBr4TYKfNgrUkOPA= +github.com/libp2p/go-libp2p-peerstore v0.2.6/go.mod h1:ss/TWTgHZTMpsU/oKVVPQCGuDHItOpf2W8RxAi50P2s= +github.com/libp2p/go-libp2p-peerstore v0.2.7/go.mod h1:ss/TWTgHZTMpsU/oKVVPQCGuDHItOpf2W8RxAi50P2s= +github.com/libp2p/go-libp2p-peerstore v0.2.8 h1:nJghUlUkFVvyk7ccsM67oFA6kqUkwyCM1G4WPVMCWYA= +github.com/libp2p/go-libp2p-peerstore v0.2.8/go.mod h1:gGiPlXdz7mIHd2vfAsHzBNAMqSDkt2UBFwgcITgw1lA= +github.com/libp2p/go-libp2p-pnet v0.2.0 h1:J6htxttBipJujEjz1y0a5+eYoiPcFHhSYHH6na5f0/k= +github.com/libp2p/go-libp2p-pnet v0.2.0/go.mod h1:Qqvq6JH/oMZGwqs3N1Fqhv8NVhrdYcO0BW4wssv21LA= +github.com/libp2p/go-libp2p-pubsub v0.5.4 h1:rHl9/Xok4zX3zgi0pg0XnUj9Xj2OeXO8oTu85q2+YA8= +github.com/libp2p/go-libp2p-pubsub v0.5.4/go.mod h1:gVOzwebXVdSMDQBTfH8ACO5EJ4SQrvsHqCmYsCZpD0E= +github.com/libp2p/go-libp2p-quic-transport v0.10.0/go.mod h1:RfJbZ8IqXIhxBRm5hqUEJqjiiY8xmEuq3HUDS993MkA= +github.com/libp2p/go-libp2p-quic-transport v0.11.2 h1:p1YQDZRHH4Cv2LPtHubqlQ9ggz4CKng/REZuXZbZMhM= +github.com/libp2p/go-libp2p-quic-transport v0.11.2/go.mod h1:wlanzKtIh6pHrq+0U3p3DY9PJfGqxMgPaGKaK5LifwQ= +github.com/libp2p/go-libp2p-record v0.1.1/go.mod h1:VRgKajOyMVgP/F0L5g3kH7SVskp17vFi2xheb5uMJtg= +github.com/libp2p/go-libp2p-record v0.1.2/go.mod h1:pal0eNcT5nqZaTV7UGhqeGqxFgGdsU/9W//C8dqjQDk= +github.com/libp2p/go-libp2p-record v0.1.3 h1:R27hoScIhQf/A8XJZ8lYpnqh9LatJ5YbHs28kCIfql0= +github.com/libp2p/go-libp2p-record v0.1.3/go.mod h1:yNUff/adKIfPnYQXgp6FQmNu3gLJ6EMg7+/vv2+9pY4= +github.com/libp2p/go-libp2p-routing v0.1.0/go.mod h1:zfLhI1RI8RLEzmEaaPwzonRvXeeSHddONWkcTcB54nE= +github.com/libp2p/go-libp2p-routing-helpers v0.2.3/go.mod h1:795bh+9YeoFl99rMASoiVgHdi5bjack0N1+AFAdbvBw= +github.com/libp2p/go-libp2p-secio v0.1.0/go.mod h1:tMJo2w7h3+wN4pgU2LSYeiKPrfqBgkOsdiKK77hE7c8= +github.com/libp2p/go-libp2p-secio v0.2.0/go.mod h1:2JdZepB8J5V9mBp79BmwsaPQhRPNN2NrnB2lKQcdy6g= +github.com/libp2p/go-libp2p-secio v0.2.1/go.mod h1:cWtZpILJqkqrSkiYcDBh5lA3wbT2Q+hz3rJQq3iftD8= +github.com/libp2p/go-libp2p-secio v0.2.2/go.mod h1:wP3bS+m5AUnFA+OFO7Er03uO1mncHG0uVwGrwvjYlNY= +github.com/libp2p/go-libp2p-swarm v0.1.0/go.mod h1:wQVsCdjsuZoc730CgOvh5ox6K8evllckjebkdiY5ta4= +github.com/libp2p/go-libp2p-swarm v0.2.1/go.mod h1:x07b4zkMFo2EvgPV2bMTlNmdQc8i+74Jjio7xGvsTgU= +github.com/libp2p/go-libp2p-swarm v0.2.2/go.mod h1:fvmtQ0T1nErXym1/aa1uJEyN7JzaTNyBcHImCxRpPKU= +github.com/libp2p/go-libp2p-swarm v0.2.3/go.mod h1:P2VO/EpxRyDxtChXz/VPVXyTnszHvokHKRhfkEgFKNM= +github.com/libp2p/go-libp2p-swarm v0.2.8/go.mod h1:JQKMGSth4SMqonruY0a8yjlPVIkb0mdNSwckW7OYziM= +github.com/libp2p/go-libp2p-swarm v0.3.0/go.mod h1:hdv95GWCTmzkgeJpP+GK/9D9puJegb7H57B5hWQR5Kk= +github.com/libp2p/go-libp2p-swarm v0.5.0/go.mod h1:sU9i6BoHE0Ve5SKz3y9WfKrh8dUat6JknzUehFx8xW4= +github.com/libp2p/go-libp2p-swarm v0.5.3 h1:hsYaD/y6+kZff1o1Mc56NcuwSg80lIphTS/zDk3mO4M= +github.com/libp2p/go-libp2p-swarm v0.5.3/go.mod h1:NBn7eNW2lu568L7Ns9wdFrOhgRlkRnIDg0FLKbuu3i8= +github.com/libp2p/go-libp2p-testing v0.0.2/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= +github.com/libp2p/go-libp2p-testing v0.0.3/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= +github.com/libp2p/go-libp2p-testing v0.0.4/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= +github.com/libp2p/go-libp2p-testing v0.1.0/go.mod h1:xaZWMJrPUM5GlDBxCeGUi7kI4eqnjVyavGroI2nxEM0= +github.com/libp2p/go-libp2p-testing v0.1.1/go.mod h1:xaZWMJrPUM5GlDBxCeGUi7kI4eqnjVyavGroI2nxEM0= +github.com/libp2p/go-libp2p-testing v0.1.2-0.20200422005655-8775583591d8/go.mod h1:Qy8sAncLKpwXtS2dSnDOP8ktexIAHKu+J+pnZOFZLTc= +github.com/libp2p/go-libp2p-testing v0.3.0/go.mod h1:efZkql4UZ7OVsEfaxNHZPzIehtsBXMrXnCfJIgDti5g= +github.com/libp2p/go-libp2p-testing v0.4.0/go.mod h1:Q+PFXYoiYFN5CAEG2w3gLPEzotlKsNSbKQ/lImlOWF0= +github.com/libp2p/go-libp2p-testing v0.4.2 h1:IOiA5mMigi+eEjf4J+B7fepDhsjtsoWA9QbsCqbNp5U= +github.com/libp2p/go-libp2p-testing v0.4.2/go.mod h1:Q+PFXYoiYFN5CAEG2w3gLPEzotlKsNSbKQ/lImlOWF0= +github.com/libp2p/go-libp2p-tls v0.1.3/go.mod h1:wZfuewxOndz5RTnCAxFliGjvYSDA40sKitV4c50uI1M= +github.com/libp2p/go-libp2p-tls v0.2.0 h1:N8i5wPiHudA+02sfW85R2nUbybPm7agjAywZc6pd3xA= +github.com/libp2p/go-libp2p-tls v0.2.0/go.mod h1:twrp2Ci4lE2GYspA1AnlYm+boYjqVruxDKJJj7s6xrc= +github.com/libp2p/go-libp2p-transport-upgrader v0.1.1/go.mod h1:IEtA6or8JUbsV07qPW4r01GnTenLW4oi3lOPbUMGJJA= +github.com/libp2p/go-libp2p-transport-upgrader v0.2.0/go.mod h1:mQcrHj4asu6ArfSoMuyojOdjx73Q47cYD7s5+gZOlns= +github.com/libp2p/go-libp2p-transport-upgrader v0.3.0/go.mod h1:i+SKzbRnvXdVbU3D1dwydnTmKRPXiAR/fyvi1dXuL4o= +github.com/libp2p/go-libp2p-transport-upgrader v0.4.2/go.mod h1:NR8ne1VwfreD5VIWIU62Agt/J18ekORFU/j1i2y8zvk= +github.com/libp2p/go-libp2p-transport-upgrader v0.4.3/go.mod h1:bpkldbOWXMrXhpZbSV1mQxTrefOg2Fi+k1ClDSA4ppw= +github.com/libp2p/go-libp2p-transport-upgrader v0.4.6 h1:SHt3g0FslnqIkEWF25YOB8UCOCTpGAVvHRWQYJ+veiI= +github.com/libp2p/go-libp2p-transport-upgrader v0.4.6/go.mod h1:JE0WQuQdy+uLZ5zOaI3Nw9dWGYJIA7mywEtP2lMvnyk= +github.com/libp2p/go-libp2p-xor v0.0.0-20210714161855-5c005aca55db/go.mod h1:LSTM5yRnjGZbWNTA/hRwq2gGFrvRIbQJscoIL/u6InY= +github.com/libp2p/go-libp2p-yamux v0.2.0/go.mod h1:Db2gU+XfLpm6E4rG5uGCFX6uXA8MEXOxFcRoXUODaK8= +github.com/libp2p/go-libp2p-yamux v0.2.1/go.mod h1:1FBXiHDk1VyRM1C0aez2bCfHQ4vMZKkAQzZbkSQt5fI= +github.com/libp2p/go-libp2p-yamux v0.2.2/go.mod h1:lIohaR0pT6mOt0AZ0L2dFze9hds9Req3OfS+B+dv4qw= +github.com/libp2p/go-libp2p-yamux v0.2.5/go.mod h1:Zpgj6arbyQrmZ3wxSZxfBmbdnWtbZ48OpsfmQVTErwA= +github.com/libp2p/go-libp2p-yamux v0.2.7/go.mod h1:X28ENrBMU/nm4I3Nx4sZ4dgjZ6VhLEn0XhIoZ5viCwU= +github.com/libp2p/go-libp2p-yamux v0.2.8/go.mod h1:/t6tDqeuZf0INZMTgd0WxIRbtK2EzI2h7HbFm9eAKI4= +github.com/libp2p/go-libp2p-yamux v0.4.0/go.mod h1:+DWDjtFMzoAwYLVkNZftoucn7PelNoy5nm3tZ3/Zw30= +github.com/libp2p/go-libp2p-yamux v0.5.0/go.mod h1:AyR8k5EzyM2QN9Bbdg6X1SkVVuqLwTGf0L4DFq9g6po= +github.com/libp2p/go-libp2p-yamux v0.5.4 h1:/UOPtT/6DHPtr3TtKXBHa6g0Le0szYuI33Xc/Xpd7fQ= +github.com/libp2p/go-libp2p-yamux v0.5.4/go.mod h1:tfrXbyaTqqSU654GTvK3ocnSZL3BuHoeTSqhcel1wsE= +github.com/libp2p/go-maddr-filter v0.0.4/go.mod h1:6eT12kSQMA9x2pvFQa+xesMKUBlj9VImZbj3B9FBH/Q= +github.com/libp2p/go-maddr-filter v0.0.5/go.mod h1:Jk+36PMfIqCJhAnaASRH83bdAvfDRp/w6ENFaC9bG+M= +github.com/libp2p/go-maddr-filter v0.1.0 h1:4ACqZKw8AqiuJfwFGq1CYDFugfXTOos+qQ3DETkhtCE= +github.com/libp2p/go-maddr-filter v0.1.0/go.mod h1:VzZhTXkMucEGGEOSKddrwGiOv0tUhgnKqNEmIAz/bPU= +github.com/libp2p/go-mplex v0.0.3/go.mod h1:pK5yMLmOoBR1pNCqDlA2GQrdAVTMkqFalaTWe7l4Yd0= +github.com/libp2p/go-mplex v0.1.0/go.mod h1:SXgmdki2kwCUlCCbfGLEgHjC4pFqhTp0ZoV6aiKgxDU= +github.com/libp2p/go-mplex v0.1.1/go.mod h1:Xgz2RDCi3co0LeZfgjm4OgUF15+sVR8SRcu3SFXI1lk= +github.com/libp2p/go-mplex v0.1.2/go.mod h1:Xgz2RDCi3co0LeZfgjm4OgUF15+sVR8SRcu3SFXI1lk= +github.com/libp2p/go-mplex v0.2.0/go.mod h1:0Oy/A9PQlwBytDRp4wSkFnzHYDKcpLot35JQ6msjvYQ= +github.com/libp2p/go-mplex v0.3.0 h1:U1T+vmCYJaEoDJPV1aq31N56hS+lJgb397GsylNSgrU= +github.com/libp2p/go-mplex v0.3.0/go.mod h1:0Oy/A9PQlwBytDRp4wSkFnzHYDKcpLot35JQ6msjvYQ= +github.com/libp2p/go-msgio v0.0.2/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ= +github.com/libp2p/go-msgio v0.0.4/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ= +github.com/libp2p/go-msgio v0.0.6 h1:lQ7Uc0kS1wb1EfRxO2Eir/RJoHkHn7t6o+EiwsYIKJA= +github.com/libp2p/go-msgio v0.0.6/go.mod h1:4ecVB6d9f4BDSL5fqvPiC4A3KivjWn+Venn/1ALLMWA= +github.com/libp2p/go-nat v0.0.3/go.mod h1:88nUEt0k0JD45Bk93NIwDqjlhiOwOoV36GchpcVc1yI= +github.com/libp2p/go-nat v0.0.4/go.mod h1:Nmw50VAvKuk38jUBcmNh6p9lUJLoODbJRvYAa/+KSDo= +github.com/libp2p/go-nat v0.0.5 h1:qxnwkco8RLKqVh1NmjQ+tJ8p8khNLFxuElYG/TwqW4Q= +github.com/libp2p/go-nat v0.0.5/go.mod h1:B7NxsVNPZmRLvMOwiEO1scOSyjA56zxYAGv1yQgRkEU= +github.com/libp2p/go-netroute v0.1.2/go.mod h1:jZLDV+1PE8y5XxBySEBgbuVAXbhtuHSdmLPL2n9MKbk= +github.com/libp2p/go-netroute v0.1.3/go.mod h1:jZLDV+1PE8y5XxBySEBgbuVAXbhtuHSdmLPL2n9MKbk= +github.com/libp2p/go-netroute v0.1.5/go.mod h1:V1SR3AaECRkEQCoFFzYwVYWvYIEtlxx89+O3qcpCl4A= +github.com/libp2p/go-netroute v0.1.6 h1:ruPJStbYyXVYGQ81uzEDzuvbYRLKRrLvTYd33yomC38= +github.com/libp2p/go-netroute v0.1.6/go.mod h1:AqhkMh0VuWmfgtxKPp3Oc1LdU5QSWS7wl0QLhSZqXxQ= +github.com/libp2p/go-openssl v0.0.2/go.mod h1:v8Zw2ijCSWBQi8Pq5GAixw6DbFfa9u6VIYDXnvOXkc0= +github.com/libp2p/go-openssl v0.0.3/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= +github.com/libp2p/go-openssl v0.0.4/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= +github.com/libp2p/go-openssl v0.0.5/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= +github.com/libp2p/go-openssl v0.0.7 h1:eCAzdLejcNVBzP/iZM9vqHnQm+XyCEbSSIheIPRGNsw= +github.com/libp2p/go-openssl v0.0.7/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= +github.com/libp2p/go-reuseport v0.0.1/go.mod h1:jn6RmB1ufnQwl0Q1f+YxAj8isJgDCQzaaxIFYDhcYEA= +github.com/libp2p/go-reuseport v0.0.2 h1:XSG94b1FJfGA01BUrT82imejHQyTxO4jEWqheyCXYvU= +github.com/libp2p/go-reuseport v0.0.2/go.mod h1:SPD+5RwGC7rcnzngoYC86GjPzjSywuQyMVAheVBD9nQ= +github.com/libp2p/go-reuseport-transport v0.0.2/go.mod h1:YkbSDrvjUVDL6b8XqriyA20obEtsW9BLkuOUyQAOCbs= +github.com/libp2p/go-reuseport-transport v0.0.3/go.mod h1:Spv+MPft1exxARzP2Sruj2Wb5JSyHNncjf1Oi2dEbzM= +github.com/libp2p/go-reuseport-transport v0.0.4/go.mod h1:trPa7r/7TJK/d+0hdBLOCGvpQQVOU74OXbNCIMkufGw= +github.com/libp2p/go-reuseport-transport v0.0.5 h1:lJzi+vSYbyJj2faPKLxNGWEIBcaV/uJmyvsUxXy2mLw= +github.com/libp2p/go-reuseport-transport v0.0.5/go.mod h1:TC62hhPc8qs5c/RoXDZG6YmjK+/YWUPC0yYmeUecbjc= +github.com/libp2p/go-sockaddr v0.0.2/go.mod h1:syPvOmNs24S3dFVGJA1/mrqdeijPxLV2Le3BRLKd68k= +github.com/libp2p/go-sockaddr v0.1.0/go.mod h1:syPvOmNs24S3dFVGJA1/mrqdeijPxLV2Le3BRLKd68k= +github.com/libp2p/go-sockaddr v0.1.1 h1:yD80l2ZOdGksnOyHrhxDdTDFrf7Oy+v3FMVArIRgZxQ= +github.com/libp2p/go-sockaddr v0.1.1/go.mod h1:syPvOmNs24S3dFVGJA1/mrqdeijPxLV2Le3BRLKd68k= +github.com/libp2p/go-stream-muxer v0.0.1/go.mod h1:bAo8x7YkSpadMTbtTaxGVHWUQsR/l5MEaHbKaliuT14= +github.com/libp2p/go-stream-muxer-multistream v0.2.0/go.mod h1:j9eyPol/LLRqT+GPLSxvimPhNph4sfYfMoDPd7HkzIc= +github.com/libp2p/go-stream-muxer-multistream v0.3.0 h1:TqnSHPJEIqDEO7h1wZZ0p3DXdvDSiLHQidKKUGZtiOY= +github.com/libp2p/go-stream-muxer-multistream v0.3.0/go.mod h1:yDh8abSIzmZtqtOt64gFJUXEryejzNb0lisTt+fAMJA= +github.com/libp2p/go-tcp-transport v0.1.0/go.mod h1:oJ8I5VXryj493DEJ7OsBieu8fcg2nHGctwtInJVpipc= +github.com/libp2p/go-tcp-transport v0.1.1/go.mod h1:3HzGvLbx6etZjnFlERyakbaYPdfjg2pWP97dFZworkY= +github.com/libp2p/go-tcp-transport v0.2.0/go.mod h1:vX2U0CnWimU4h0SGSEsg++AzvBcroCGYw28kh94oLe0= +github.com/libp2p/go-tcp-transport v0.2.4/go.mod h1:9dvr03yqrPyYGIEN6Dy5UvdJZjyPFvl1S/igQ5QD1SU= +github.com/libp2p/go-tcp-transport v0.2.7/go.mod h1:lue9p1b3VmZj1MhhEGB/etmvF/nBQ0X9CW2DutBT3MM= +github.com/libp2p/go-tcp-transport v0.2.8 h1:aLjX+Nkz+kIz3uA56WtlGKRSAnKDvnqKmv1qF4EyyE4= +github.com/libp2p/go-tcp-transport v0.2.8/go.mod h1:64rSfVidkYPLqbzpcN2IwHY4pmgirp67h++hZ/rcndQ= +github.com/libp2p/go-ws-transport v0.1.0/go.mod h1:rjw1MG1LU9YDC6gzmwObkPd/Sqwhw7yT74kj3raBFuo= +github.com/libp2p/go-ws-transport v0.1.2/go.mod h1:dsh2Ld8F+XNmzpkaAijmg5Is+e9l6/1tK/6VFOdN69Y= +github.com/libp2p/go-ws-transport v0.2.0/go.mod h1:9BHJz/4Q5A9ludYWKoGCFC5gUElzlHoKzu0yY9p/klM= +github.com/libp2p/go-ws-transport v0.3.0/go.mod h1:bpgTJmRZAvVHrgHybCVyqoBmyLQ1fiZuEaBYusP5zsk= +github.com/libp2p/go-ws-transport v0.4.0/go.mod h1:EcIEKqf/7GDjth6ksuS/6p7R49V4CBY6/E7R/iyhYUA= +github.com/libp2p/go-ws-transport v0.5.0 h1:cO6x4P0v6PfxbKnxmf5cY2Ny4OPDGYkUqNvZzp/zdlo= +github.com/libp2p/go-ws-transport v0.5.0/go.mod h1:I2juo1dNTbl8BKSBYo98XY85kU2xds1iamArLvl8kNg= +github.com/libp2p/go-yamux v1.2.2/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= +github.com/libp2p/go-yamux v1.2.3/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= +github.com/libp2p/go-yamux v1.3.0/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= +github.com/libp2p/go-yamux v1.3.3/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= +github.com/libp2p/go-yamux v1.3.5/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= +github.com/libp2p/go-yamux v1.3.7/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= +github.com/libp2p/go-yamux v1.4.0/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= +github.com/libp2p/go-yamux v1.4.1 h1:P1Fe9vF4th5JOxxgQvfbOHkrGqIZniTLf+ddhZp8YTI= +github.com/libp2p/go-yamux v1.4.1/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= +github.com/libp2p/go-yamux/v2 v2.2.0 h1:RwtpYZ2/wVviZ5+3pjC8qdQ4TKnrak0/E01N1UWoAFU= +github.com/libp2p/go-yamux/v2 v2.2.0/go.mod h1:3So6P6TV6r75R9jiBpiIKgU/66lOarCZjqROGxzPpPQ= +github.com/libp2p/zeroconf/v2 v2.0.0 h1:qYAHAqUVh4hMSfu+iDTZNqH07wLGAvb1+DW4Tx/qUoQ= +github.com/libp2p/zeroconf/v2 v2.0.0/go.mod h1:J85R/d9joD8u8F9aHM8pBXygtG9W02enEwS+wWeL6yo= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= +github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= +github.com/lotus-king/SM3 v0.0.0-20161018102718-5a3b85ca5a40/go.mod h1:BZEQO3B4MHBIfALOrsqsiZ1hNMdVZ0j7E4q6zXLh2aM= +github.com/lucas-clemente/quic-go v0.19.3/go.mod h1:ADXpNbTQjq1hIzCpB+y/k5iz4n4z4IwqoLb94Kh5Hu8= +github.com/lucas-clemente/quic-go v0.21.2 h1:8LqqL7nBQFDUINadW0fHV/xSaCQJgmJC0Gv+qUnjd78= +github.com/lucas-clemente/quic-go v0.21.2/go.mod h1:vF5M1XqhBAHgbjKcJOXY3JZz3GP0T3FQhz/uyOUS38Q= +github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= +github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= +github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc= +github.com/marten-seemann/qtls v0.10.0/go.mod h1:UvMd1oaYDACI99/oZUYLzMCkBXQVT0aGm99sJhbT8hs= +github.com/marten-seemann/qtls-go1-15 v0.1.1/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= +github.com/marten-seemann/qtls-go1-15 v0.1.4/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= +github.com/marten-seemann/qtls-go1-15 v0.1.5 h1:Ci4EIUN6Rlb+D6GmLdej/bCQ4nPYNtVXQB+xjiXE1nk= +github.com/marten-seemann/qtls-go1-15 v0.1.5/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= +github.com/marten-seemann/qtls-go1-16 v0.1.4 h1:xbHbOGGhrenVtII6Co8akhLEdrawwB2iHl5yhJRpnco= +github.com/marten-seemann/qtls-go1-16 v0.1.4/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk= +github.com/marten-seemann/qtls-go1-17 v0.1.0-rc.1 h1:/rpmWuGvceLwwWuaKPdjpR4JJEUH0tq64/I3hvzaNLM= +github.com/marten-seemann/qtls-go1-17 v0.1.0-rc.1/go.mod h1:fz4HIxByo+LlWcreM4CZOYNuz3taBQ8rN2X6FqvaWo8= +github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk= +github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU= +github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA= +github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA= +github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= +github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.12/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.22/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/miekg/dns v1.1.28/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= +github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg= +github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= +github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c h1:bzE/A84HN25pxAuk9Eej1Kz9OUelF97nAc82bDquQI8= +github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c/go.mod h1:0SQS9kMwD2VsyFEB++InYyBJroV/FRmBgcydeSUcJms= +github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b h1:z78hV3sbSMAUoyUMM0I83AUIT6Hu17AWfgjzIbtrYFc= +github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b/go.mod h1:lxPUiZwKoFL8DUUmalo2yJJUCxbPKtm8OKfqr2/FTNU= +github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc h1:PTfri+PuQmWDqERdnNMiD9ZejrlswWrCpBEZgWOiTrc= +github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc/go.mod h1:cGKTAVKx4SxOuR/czcZ/E2RSJ3sfHs8FpHhQ5CWMf9s= +github.com/mileusna/useragent v0.0.0-20190129205925-3e331f0949a5/go.mod h1:JWhYAp2EXqUtsxTKdeGlY8Wp44M7VxThC9FEoNGi2IE= +github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 h1:lYpkrQH5ajf0OXOcUbGjvZxxijuBwbbmlSxLiuofa+g= +github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= +github.com/minio/md5-simd v1.1.0 h1:QPfiOqlZH+Cj9teu0t9b1nTBfPbyTl16Of5MeuShdK4= +github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw= +github.com/minio/minio-go/v7 v7.0.12 h1:/4pxUdwn9w0QEryNkrrWaodIESPRX+NxpO0Q6hVdaAA= +github.com/minio/minio-go/v7 v7.0.12/go.mod h1:S23iSP5/gbMwtxeY5FM71R+TkAYyzEdoNEDDwpt8yWs= +github.com/minio/sha256-simd v0.0.0-20190131020904-2d45a736cd16/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= +github.com/minio/sha256-simd v0.0.0-20190328051042-05b4dd3047e5/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= +github.com/minio/sha256-simd v0.1.0/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= +github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= +github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= +github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= +github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.2.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v0.0.0-20180511053014-58118c1ea916/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= +github.com/mr-tron/base58 v1.1.1/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= +github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= +github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= +github.com/multiformats/go-base32 v0.0.3 h1:tw5+NhuwaOjJCC5Pp82QuXbrmLzWg7uxlMFp8Nq/kkI= +github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA= +github.com/multiformats/go-base36 v0.1.0 h1:JR6TyF7JjGd3m6FbLU2cOxhC0Li8z8dLNGQ89tUg4F4= +github.com/multiformats/go-base36 v0.1.0/go.mod h1:kFGE83c6s80PklsHO9sRn2NCoffoRdUUOENyW/Vv6sM= +github.com/multiformats/go-multiaddr v0.0.1/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= +github.com/multiformats/go-multiaddr v0.0.2/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= +github.com/multiformats/go-multiaddr v0.0.4/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= +github.com/multiformats/go-multiaddr v0.1.0/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= +github.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo= +github.com/multiformats/go-multiaddr v0.2.0/go.mod h1:0nO36NvPpyV4QzvTLi/lafl2y95ncPj0vFwVF6k6wJ4= +github.com/multiformats/go-multiaddr v0.2.1/go.mod h1:s/Apk6IyxfvMjDafnhJgJ3/46z7tZ04iMk5wP4QMGGE= +github.com/multiformats/go-multiaddr v0.2.2/go.mod h1:NtfXiOtHvghW9KojvtySjH5y0u0xW5UouOmQQrn6a3Y= +github.com/multiformats/go-multiaddr v0.3.0/go.mod h1:dF9kph9wfJ+3VLAaeBqo9Of8x4fJxp6ggJGteB8HQTI= +github.com/multiformats/go-multiaddr v0.3.1/go.mod h1:uPbspcUPd5AfaP6ql3ujFY+QWzmBD8uLLL4bXW0XfGc= +github.com/multiformats/go-multiaddr v0.3.3/go.mod h1:lCKNGP1EQ1eZ35Za2wlqnabm9xQkib3fyB+nZXHLag0= +github.com/multiformats/go-multiaddr v0.4.0/go.mod h1:YcpyLH8ZPudLxQlemYBPhSm0/oCXAT8Z4mzFpyoPyRc= +github.com/multiformats/go-multiaddr v0.4.1 h1:Pq37uLx3hsyNlTDir7FZyU8+cFCTqd5y1KiM2IzOutI= +github.com/multiformats/go-multiaddr v0.4.1/go.mod h1:3afI9HfVW8csiF8UZqtpYRiDyew8pRX7qLIGHu9FLuM= +github.com/multiformats/go-multiaddr-dns v0.0.1/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= +github.com/multiformats/go-multiaddr-dns v0.0.2/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= +github.com/multiformats/go-multiaddr-dns v0.0.3/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= +github.com/multiformats/go-multiaddr-dns v0.1.0/go.mod h1:01k2RAqtoXIuPa3DCavAE9/6jc6nM0H3EgZyfUhN2oY= +github.com/multiformats/go-multiaddr-dns v0.2.0/go.mod h1:TJ5pr5bBO7Y1B18djPuRsVkduhQH2YqYSbxWJzYGdK0= +github.com/multiformats/go-multiaddr-dns v0.3.1 h1:QgQgR+LQVt3NPTjbrLLpsaT2ufAA2y0Mkk+QRVJbW3A= +github.com/multiformats/go-multiaddr-dns v0.3.1/go.mod h1:G/245BRQ6FJGmryJCrOuTdB37AMA5AMOVuO6NY3JwTk= +github.com/multiformats/go-multiaddr-fmt v0.0.1/go.mod h1:aBYjqL4T/7j4Qx+R73XSv/8JsgnRFlf0w2KGLCmXl3Q= +github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E= +github.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo= +github.com/multiformats/go-multiaddr-net v0.0.1/go.mod h1:nw6HSxNmCIQH27XPGBuX+d1tnvM7ihcFwHMSstNAVUU= +github.com/multiformats/go-multiaddr-net v0.1.0/go.mod h1:5JNbcfBOP4dnhoZOv10JJVkJO0pCCEf8mTnipAo2UZQ= +github.com/multiformats/go-multiaddr-net v0.1.1/go.mod h1:5JNbcfBOP4dnhoZOv10JJVkJO0pCCEf8mTnipAo2UZQ= +github.com/multiformats/go-multiaddr-net v0.1.2/go.mod h1:QsWt3XK/3hwvNxZJp92iMQKME1qHfpYmyIjFVsSOY6Y= +github.com/multiformats/go-multiaddr-net v0.1.3/go.mod h1:ilNnaM9HbmVFqsb/qcNysjCu4PVONlrBZpHIrw/qQuA= +github.com/multiformats/go-multiaddr-net v0.1.4/go.mod h1:ilNnaM9HbmVFqsb/qcNysjCu4PVONlrBZpHIrw/qQuA= +github.com/multiformats/go-multiaddr-net v0.1.5/go.mod h1:ilNnaM9HbmVFqsb/qcNysjCu4PVONlrBZpHIrw/qQuA= +github.com/multiformats/go-multiaddr-net v0.2.0/go.mod h1:gGdH3UXny6U3cKKYCvpXI5rnK7YaOIEOPVDI9tsJbEA= +github.com/multiformats/go-multibase v0.0.1/go.mod h1:bja2MqRZ3ggyXtZSEDKpl0uO/gviWFaSteVbWT51qgs= +github.com/multiformats/go-multibase v0.0.3 h1:l/B6bJDQjvQ5G52jw4QGSYeOTZoAwIO77RblWplfIqk= +github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc= +github.com/multiformats/go-multicodec v0.1.6/go.mod h1:lliaRHbcG8q33yf4Ot9BGD7JqR/Za9HE7HTyVyKwrUQ= +github.com/multiformats/go-multicodec v0.2.0 h1:MUzKZWxOFagwLLtlx96pub9zwDQAbMAf1k9fXOdc3so= +github.com/multiformats/go-multicodec v0.2.0/go.mod h1:/y4YVwkfMyry5kFbMTbLJKErhycTIftytRV+llXdyS4= +github.com/multiformats/go-multihash v0.0.1/go.mod h1:w/5tugSrLEbWqlcgJabL3oHFKTwfvkofsjW2Qa1ct4U= +github.com/multiformats/go-multihash v0.0.5/go.mod h1:lt/HCbqlQwlPBz7lv0sQCdtfcMtlJvakRUn/0Ual8po= +github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= +github.com/multiformats/go-multihash v0.0.9/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= +github.com/multiformats/go-multihash v0.0.10/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= +github.com/multiformats/go-multihash v0.0.13/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= +github.com/multiformats/go-multihash v0.0.14/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= +github.com/multiformats/go-multihash v0.0.15 h1:hWOPdrNqDjwHDx82vsYGSDZNyktOJJ2dzZJzFkOV1jM= +github.com/multiformats/go-multihash v0.0.15/go.mod h1:D6aZrWNLFTV/ynMpKsNtB40mJzmCl4jb1alC0OvHiHg= +github.com/multiformats/go-multistream v0.1.0/go.mod h1:fJTiDfXJVmItycydCnNx4+wSzZ5NwG2FEVAI30fiovg= +github.com/multiformats/go-multistream v0.1.1/go.mod h1:KmHZ40hzVxiaiwlj3MEbYgK9JFk2/9UktWZAF54Du38= +github.com/multiformats/go-multistream v0.2.1/go.mod h1:5GZPQZbkWOLOn3J2y4Y99vVW7vOfsAflxARk3x14o6k= +github.com/multiformats/go-multistream v0.2.2 h1:TCYu1BHTDr1F/Qm75qwYISQdzGcRdC21nFgQW7l7GBo= +github.com/multiformats/go-multistream v0.2.2/go.mod h1:UIcnm7Zuo8HKG+HkWgfQsGL+/MIEhyTqbODbIUwSXKs= +github.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= +github.com/multiformats/go-varint v0.0.2/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= +github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= +github.com/multiformats/go-varint v0.0.6 h1:gk85QWKxh3TazbLxED/NlDVv8+q+ReFJk7Y2W/KhfNY= +github.com/multiformats/go-varint v0.0.6/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= +github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= +github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.16.0/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= +github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.11.0/go.mod h1:azGKhqFUon9Vuj0YmTfLSmx0FUwqXYSTl5re8lQLTUg= +github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak= +github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= +github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/oofpgDLD/dtask v1.0.2/go.mod h1:r07DPvHuXY0iEq7QSFBZ5gVYRmi3Ohv6d8rek5CNPuk= +github.com/oofpgDLD/u-push v0.0.2 h1:KeLULue0eq2MJLVz7KaN2dgZsaIbBo9CIx2z0+XqY5w= +github.com/oofpgDLD/u-push v0.0.2/go.mod h1:mHaPUV7z00PP3pfrjUfGHBg4AGwHPYn2uTwBlBrjbbc= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing-contrib/go-stdlib v0.0.0-20190519235532-cf7a6c988dc9/go.mod h1:PLldrQSroqzH70Xl+1DQcGnefIbqsKR7UDaiux3zV+w= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.0.3-0.20180606204148-bd9c31933947/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/otiai10/copy v1.7.0 h1:hVoPiN+t+7d2nzzwMiDHPSOogsWAStewq3TwU05+clE= +github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U= +github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= +github.com/otiai10/curr v1.0.0 h1:TJIWdbX0B+kpNagQrjgq8bCMrbhiuX73M2XwgtDMoOI= +github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= +github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= +github.com/otiai10/mint v1.3.3 h1:7JgpsBaN0uMkyju4tbYHu0mnM55hNKVYLsXmwr15NQI= +github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= +github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pegasus-kv/thrift v0.13.0 h1:4ESwaNoHImfbHa9RUGJiJZ4hrxorihZHk5aarYwY8d4= +github.com/pegasus-kv/thrift v0.13.0/go.mod h1:Gl9NT/WHG6ABm6NsrbfE8LiJN0sAyneCrvB4qN4NPqQ= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= +github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= +github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pierrec/lz4 v2.5.1+incompatible h1:Yq0up0149Hh5Ekhm/91lgkZuD1ZDnXNM26bycpTzYBM= +github.com/pierrec/lz4 v2.5.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= +github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/polydawn/refmt v0.0.0-20190807091052-3d65705ee9f1 h1:CskT+S6Ay54OwxBGB0R3Rsx4Muto6UnEYTyKJbyRIAI= +github.com/polydawn/refmt v0.0.0-20190807091052-3d65705ee9f1/go.mod h1:uIp+gprXxxrWSjjklXD+mN4wed/tMfjMMmN/9+JsA9o= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/alertmanager v0.20.0/go.mod h1:9g2i48FAyZW6BtbsnvHtMHQXl2aVtrORKwKVCQ+nbrg= +github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66IdsO+O441Eve7ptJDU= +github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU= +github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= +github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.30.0 h1:JEkYlQnpzrzQFxi6gnukFPdQ+ac82oRhzMcIduJu/Ug= +github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/prometheus v0.0.0-20200609090129-a6600f564e3c/go.mod h1:S5n0C6tSgdnwWshBUceRx5G1OsjLv/EeZ9t3wIfEtsY= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/qianlnk/pgbar v0.0.0-20210208085217-8c19b9f2477e/go.mod h1:4YWkn3EVkh8c1BDlVmw+Zh2QLhs+MbAg4xy4RqcKMsA= +github.com/qianlnk/to v0.0.0-20191230085244-91e712717368/go.mod h1:HYAQIJIdgW9cGr75BDsucQMgKREt00mECJHOskH5n5k= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563 h1:dY6ETXrvDG7Sa4vE8ZQG4yqWg6UnOcbqTAahkV813vQ= +github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= +github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc= +github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rs/cors v1.6.0 h1:G9tHG9lebljV9mfp9SNPDL36nCDxmo3zTlAf1YgvzmI= +github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/xid v1.3.0 h1:6NjYksEUlhurdVehpc7S7dk6DAmcKv8V9gG0FsVN2U4= +github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.21.0 h1:Q3vdXlfLNT+OftyBHsU0Y445MD+8m8axjKgf2si0QcM= +github.com/rs/zerolog v1.21.0/go.mod h1:ZPhntP/xmq1nnND05hhpAh2QMhSsA4UN3MGZ6O2J3hM= +github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/satori/go.uuid v0.0.0-20160603004225-b111a074d5ef/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b h1:gQZ0qzfKHQIybLANtM3mBXNUtOfsCFXeTsnBqCsx1KM= +github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= +github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= +github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= +github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= +github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= +github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw= +github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI= +github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU= +github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag= +github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg= +github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw= +github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y= +github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= +github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= +github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q= +github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ= +github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I= +github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0= +github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ= +github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk= +github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= +github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= +github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/smartystreets/assertions v0.0.0-20180301161246-7678a5452ebe/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= +github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/gunit v0.0.0-20180314194857-6f0d6275bdcd/go.mod h1:XUKj4gbqj2QvJk/OdLWzyZ3FYli0f+MdpngyryX0gcw= +github.com/smola/gocompat v0.2.0/go.mod h1:1B0MlxbmoZNo3h8guHp8HztB3BSYR5itql9qtVc0ypY= +github.com/snowflakedb/gosnowflake v1.3.13/go.mod h1:6nfka9aTXkUNha1p1cjeeyjDvcyh7jfjp0l8kGpDBok= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= +github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= +github.com/spacemonkeygo/openssl v0.0.0-20181017203307-c2dcc5cca94a/go.mod h1:7AyxJNCJ7SBZ1MfVQCWD6Uqo2oubI2Eq2y2eqf+A5r0= +github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 h1:RC6RW7j+1+HkWaX/Yh71Ee5ZHaHYt7ZP4sQgUrm6cDU= +github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572/go.mod h1:w0SWMsp6j9O/dk4/ZpIhL+3CkG8ofA2vuv7k+ltqUMc= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= +github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/src-d/envconfig v1.0.0/go.mod h1:Q9YQZ7BKITldTBnoxsE5gOeB5y66RyPXeue/R4aaNBc= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.3-0.20181224173747-660f15d67dbb/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14 h1:PyYN9JH5jY9j6av01SpfRMb+1DWg/i3MbGOKPxJ2wjM= +github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E= +github.com/swaggo/gin-swagger v1.1.0 h1:ZI6/82S07DkkrMfGKbJhKj1R+QNTICkeAJP06pU36pU= +github.com/swaggo/gin-swagger v1.1.0/go.mod h1:FQlm07YuT1glfN3hQiO11UQ2m39vOCZ/aa3WWr5E+XU= +github.com/swaggo/swag v1.4.0/go.mod h1:hog2WgeMOrQ/LvQ+o1YGTeT+vWVrbi0SiIslBtxKTyM= +github.com/swaggo/swag v1.7.8 h1:w249t0l/kc/DKMGlS0fppNJQxKyJ8heNaUWB6nsH3zc= +github.com/swaggo/swag v1.7.8/go.mod h1:gZ+TJ2w/Ve1RwQsA2IRoSOTidHz6DX+PIG8GWvbnoLU= +github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= +github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= +github.com/tal-tech/go-zero v1.1.6/go.mod h1:LbN0C7/rbl2+LUWTSUYx5leXmgedeMWjt1jc3/8/zFA= +github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= +github.com/tencentyun/tls-sig-api-v2-golang v1.2.0 h1:N6hWlyY5HWa9QoezVVIIXi5TDZSyX+UvA8pn/tcezS4= +github.com/tencentyun/tls-sig-api-v2-golang v1.2.0/go.mod h1:D/i1nQrP1hSXxxz+SAmjaprAwH3EhYaktJG6BObWnFE= +github.com/tendermint/ed25519 v0.0.0-20171027050219-d8387025d2b9/go.mod h1:nt45hbhDkWVdMBkr2TOgOzCrpBccXdN09WOiOYTHVEk= +github.com/thinkboy/log4go v0.0.0-20160303045050-f91a411e4a18/go.mod h1:IeFvD+ls8ldW9O62n+3QrktXSvtXuUDi6iLnISh6q80= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= +github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= +github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8/go.mod h1:XxO4hdhhrzAd+G4CjDqaOkd0hUzmtPR/d3EiBBMn/wc= +github.com/tjfoc/gmsm v1.3.2 h1:7JVkAn5bvUJ7HtU08iW6UiD+UTmJTIToHCfeFzkcCxM= +github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/uber-go/tally v3.3.15+incompatible/go.mod h1:YDTIBxdXyOU/sCWilKB4bgyufu1cEi0jdVnRdxvjnmU= +github.com/uber/athenadriver v1.1.4/go.mod h1:tQjho4NzXw55LGfSZEcETuYydpY1vtmixUabHkC1K/E= +github.com/uber/jaeger-client-go v2.23.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-client-go v2.28.0+incompatible h1:G4QSBfvPKvg5ZM2j9MrJFdfI5iSljY/WnJqOGFao6HI= +github.com/uber/jaeger-client-go v2.28.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-lib v2.2.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= +github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= +github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= +github.com/ugorji/go v0.0.0-20180407103000-f3cacc17c85e/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= +github.com/ugorji/go v1.1.2/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go v1.1.13 h1:nB3O5kBSQGjEQAcfe1aLUYuxmXdFKmYgBZhY32rQb6Q= +github.com/ugorji/go v1.1.13/go.mod h1:jxau1n+/wyTGLQoCkjok9r5zFa/FxT6eI5HiHKQszjc= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ugorji/go/codec v0.0.0-20181209151446-772ced7fd4c2/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ugorji/go/codec v0.0.0-20190204201341-e444a5086c43/go.mod h1:iT03XoTwV7xq/+UGwKO3UbC1nNNlopQiY61beSdrtOA= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/ugorji/go/codec v1.1.13 h1:013LbFhocBoIqgHeIHKlV4JWYhqogATYWZhIcH0WHn4= +github.com/ugorji/go/codec v1.1.13/go.mod h1:oNVt3Dq+FO91WNQ/9JnHKQP2QJxTzoN7wCBFCq1OeuU= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU= +github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= +github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.5.0/go.mod h1:eriCz9OhZjKCGfJ185a/IDgNl0bg9IbzfpcslMZXU1c= +github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= +github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= +github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= +github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= +github.com/wangjia184/sortedset v0.0.0-20160527075905-f5d03557ba30/go.mod h1:YkocrP2K2tcw938x9gCOmT5G5eCD6jsTz0SZuyAqwIE= +github.com/warpfork/go-wish v0.0.0-20200122115046-b9ea61034e4a h1:G++j5e0OC488te356JvdhaM8YS6nMsjLAYF7JxCv07w= +github.com/warpfork/go-wish v0.0.0-20200122115046-b9ea61034e4a/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= +github.com/whyrusleeping/base32 v0.0.0-20170828182744-c30ac30633cc/go.mod h1:r45hJU7yEoA81k6MWNhpMj/kms0n14dkzkxYHoB96UM= +github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 h1:EKhdznlJHPMoKr0XTrX+IlJs1LH3lyx2nfr1dOlZ79k= +github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc= +github.com/whyrusleeping/go-logging v0.0.0-20170515211332-0457bb6b88fc/go.mod h1:bopw91TMyo8J3tvftk8xmU2kPmlrt4nScJQZU2hE5EM= +github.com/whyrusleeping/go-logging v0.0.1/go.mod h1:lDPYj54zutzG1XYfHAhcc7oNXEburHQBn+Iqd4yS4vE= +github.com/whyrusleeping/go-notifier v0.0.0-20170827234753-097c5d47330f/go.mod h1:cZNvX9cFybI01GriPRMXDtczuvUhgbcYr9iCGaNlRv8= +github.com/whyrusleeping/mafmt v1.2.8/go.mod h1:faQJFPbLSxzD9xpA02ttW/tS9vZykNvXwGvqIpk20FA= +github.com/whyrusleeping/mdns v0.0.0-20180901202407-ef14215e6b30/go.mod h1:j4l84WPFclQPj320J9gp0XwNKBb3U0zt5CBqjPp22G4= +github.com/whyrusleeping/mdns v0.0.0-20190826153040-b9b60ed33aa9 h1:Y1/FEOpaCpD21WxrmfeIYCFPuVPRCY2XZTWzTNHGw30= +github.com/whyrusleeping/mdns v0.0.0-20190826153040-b9b60ed33aa9/go.mod h1:j4l84WPFclQPj320J9gp0XwNKBb3U0zt5CBqjPp22G4= +github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7 h1:E9S12nwJwEOXe2d6gT6qxdvqMnNq+VnSsKPgm2ZZNds= +github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7/go.mod h1:X2c0RVCI1eSUFI8eLcY3c0423ykwiUdxLJtkDvruhjI= +github.com/whyrusleeping/timecache v0.0.0-20160911033111-cfcb2f1abfee h1:lYbXeSvJi5zk5GLKVuid9TVjS9a0OmLIDKTfoZBL6Ow= +github.com/whyrusleeping/timecache v0.0.0-20160911033111-cfcb2f1abfee/go.mod h1:m2aV4LZI4Aez7dP5PMyVKEHhUyEJ/RjmPEDOpDvudHg= +github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= +github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= +github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= +github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= +github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2/go.mod h1:hzfGeIUDq/j97IG+FhNqkowIyEcD88LrW6fyU3K3WqY= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ= +github.com/zhenjl/cityhash v0.0.0-20131128155616-cdd6a94144ab/go.mod h1:P6L88wrqK99Njntah9SB7AyzFpUXsXYq06LkjixxQmY= +gitlab.33.cn/btrade/auto_trade_tools v1.2.8/go.mod h1:XbudTl/FDkpdcGDKwDIZ0QTuY2r9NVElcE/e9afixv8= +gitlab.33.cn/btrade/blockchain v1.1.0/go.mod h1:4XRBV9vjiAB2tQbXAtWCoAcI55OmNIVu9Gc8yrzg1cM= +gitlab.33.cn/btrade/common v1.0.2/go.mod h1:BJhb/g0OgtLBNJxS8zZA54jTtr6oY0/T5N5jTKbIxLY= +gitlab.33.cn/btrade/crypto v1.0.1/go.mod h1:FYhKLN69inc9nhXJw49qodvRRmFQCqEyj/JhfeNLfZk= +gitlab.33.cn/btrade/log v1.0.2/go.mod h1:grfvrYpf0OJOJ5HCUefcPlaOunEc53wOXw4WnAHLNwM= +gitlab.33.cn/chat/im v1.0.6-pre2 h1:5uGtJoOwC3szqrBXjWUE7Cx6rlBI2yOIM1rp5siffTA= +gitlab.33.cn/chat/im v1.0.6-pre2/go.mod h1:I54oIHXtpMDGk1xJVYrqsDwTlITkML4GjXo7XBoIKRs= +gitlab.33.cn/chat/im v1.0.10 h1:ryzN+iohNiCTjZvLLl7BnVt6tof6hPeZTAekpkwclGE= +gitlab.33.cn/chat/im v1.0.10/go.mod h1:JnuKyF2919kRWmkOWuCM1+zI9OM38C4yfKGznU4a8r0= +gitlab.33.cn/chat/im-pkg v0.0.1 h1:kFU4SVq7xC0lvufGv4zjTzVKn5BPjqa0Am5IYWPKbOU= +gitlab.33.cn/chat/im-pkg v0.0.1/go.mod h1:5kmgKeovttxiKqaNqGQovwIm3v3ZZWPxD/xGj8teBj0= +gitlab.33.cn/chat/imparse v1.0.6 h1:xYrY5iVkCWMXbNIZjYIkEhJ6sZ88U0Qgib8pRJv4GHQ= +gitlab.33.cn/chat/imparse v1.0.6/go.mod h1:r47sGdJ8sMfpYs03H8Uxw44EEq+FfapXV1APjoos3KA= +gitlab.33.cn/contract/exchange v1.0.7/go.mod h1:MylnHconA4rUfp10jWZc6zZ+3LBIt13Ow9HUWGACAto= +gitlab.33.cn/utils/go-kit v1.0.7 h1:RYUI55pD7MT+DmUnCWWUcCY8oRzWHqmSyBwPovnoioU= +gitlab.33.cn/utils/go-kit v1.0.7/go.mod h1:F5uJaHYMvCoEeyHwLtwQd1PnmbN//DuS5UzSo4WfVBI= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.etcd.io/etcd v0.0.0-20200402134248-51bdeb39e698 h1:jWtjCJX1qxhHISBMLRztWwR+EXkI7MJAF2HjHAE/x/I= +go.etcd.io/etcd v0.0.0-20200402134248-51bdeb39e698/go.mod h1:YoUyTScD3Vcv2RBm3eGVOq7i1ULiz3OuXoQFWOirmAM= +go.etcd.io/etcd/api/v3 v3.5.0 h1:GsV3S+OfZEOCNXdtNkBSR7kgLobAa/SO6tCxRa0GAYw= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0 h1:2aQv6F436YnN7I4VbI8PPYrBhu+SmrTaADcf8Mi/6PU= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v3 v3.5.0 h1:62Eh0XOro+rDwkrypAGDfgmNh5Joq+z+W9HZdlXMzek= +go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= +go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.3.0/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= +go.mongodb.org/mongo-driver v1.3.2/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= +go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= +go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= +go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= +go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.5.1/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/automaxprocs v1.3.0/go.mod h1:9CWT6lKIep8U41DDaPiH6eFscnTyjfTANNQNx6LrIcA= +go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.2.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.7.0 h1:zaiO/rmgFjbmCXdSYJWQcdvOCsthmdaHfr3Gm2Kx4Ec= +go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.14.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= +go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= +go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +go.uber.org/zap v1.19.0 h1:mZQZefskPPCMIBCSEH0v2/iUqqLrYtaeqwD6FUGUnFE= +go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= +golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180505025534-4ec37c66abab/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190225124518-7f87c0fbb88b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= +golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200117160349-530e935923ad/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200422194213-44a606286825/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 h1:QE6XYQK6naiK1EPAe1g/ILLxN5RBoH5xkJk3CqlMI/Y= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.0 h1:UG21uOlmZabA4fW5i7ZX6bjw1xELEGg/ZLgZq9auk/Q= +golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190227160552-c95aed5357e7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191105084925-a882066a44e0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= +golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211020060615-d418f374d309 h1:A0lJIi+hcTR6aajJH4YqKWwohY4aW9RO7oRMcdv+HKI= +golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190219092855-153ac476189d/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190526052359-791d8a0f4d09/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191206220618-eeba5f6aabab/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210317225723-c4fcb01b228e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210426080607-c94f62235c83/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210909193231-528a39cd75f3/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359 h1:2B5p2L5IfGiD7+b9BOoRMC6DgObAVZV+Fsp050NqXik= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE= +golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181130052023-1c3d964395ce/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190110015856-aa033095749b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190813034749-528a2984e271/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191203134012-c197fd4bf371/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200108203644-89082a384178/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304024140-c4206d458c3f/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200410132612-ae9902aceb98/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200422205258-72e4a01eba43/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200721032237-77f530d86f9a/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ= +golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.6.0/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU= +gonum.org/v1/gonum v0.8.2 h1:CCXrcPKiGGotvnN6jfUsKk4rRqm7q09/YbKb5xCEvtM= +gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= +gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPjXu+YNeGvK9VTSHY6+Qihc= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= +google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= +google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200108215221-bd8f9a0ef82f/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200310143817-43be25429f5a/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200420144010-e5e8543f8aeb/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c h1:wtujag7C+4D6KMoulW9YauvK2lgdvCMS260jsqqBXr0= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/grpc v0.0.0-20181030232906-a88340f3c899/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= +google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.22.3/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.28.1/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.0/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.0/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/Shopify/sarama.v1 v1.19.0 h1:yvI/R1jfMpKvvwmX4r/AQjaI5oszWEOlvKxUdaj53OM= +gopkg.in/Shopify/sarama.v1 v1.19.0/go.mod h1:AxnvoaevB2nBjNK17cG61A3LleFcWFwVBHBt+cot4Oc= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/fsnotify/fsnotify.v1 v1.4.7/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ= +gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= +gopkg.in/go-playground/webhooks.v5 v5.2.0/go.mod h1:LZbya/qLVdbqDR1aKrGuWV6qbia2zCYSR5dpom2SInQ= +gopkg.in/h2non/gock.v1 v1.0.8/go.mod h1:KHI4Z1sxDW6P4N3DfTWSEza07YpkQP7KJBfglRMEjKY= +gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww= +gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/natefinch/lumberjack.v2 v2.0.0-20170531160350-a96e63847dc3/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78= +gopkg.in/src-d/go-cli.v0 v0.0.0-20181105080154-d492247bbc0d/go.mod h1:z+K8VcOYVYcSwSjGebuDL6176A1XskgbtNl64NSg+n8= +gopkg.in/src-d/go-log.v1 v1.0.1/go.mod h1:GN34hKP0g305ysm2/hctJ0Y8nWP3zxXXJ8GFabTyABE= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 h1:yiW+nvdHb9LVqSHQBXfZCieqV4fzYhNBql77zY0ykqs= +gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637/go.mod h1:BHsqpu/nsuzkT5BpiH1EMZPLyqSMM8JbIavyFACoFNk= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +k8s.io/api v0.17.5/go.mod h1:0zV5/ungglgy2Rlm3QK8fbxkXVs+BSJWpJP/+8gUVLY= +k8s.io/apimachinery v0.0.0-20191123233150-4c4803ed55e3/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= +k8s.io/apimachinery v0.17.5 h1:QAjfgeTtSGksdkgyaPrIb4lhU16FWMIzxKejYD5S0gc= +k8s.io/apimachinery v0.17.5/go.mod h1:ioIo1G/a+uONV7Tv+ZmCbMG1/a3kVw5YcDdncd8ugQ0= +k8s.io/client-go v0.17.5/go.mod h1:S8uZpBpjJJdEH/fEyxcqg7Rn0P5jH+ilkgBHjriSmNo= +k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= +k8s.io/kube-openapi v0.0.0-20200316234421-82d701f24f9d/go.mod h1:F+5wygcW0wmRTnM3cOgIqGivxkwSWIWT5YdsDbeAOaU= +k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +k8s.io/utils v0.0.0-20200414100711-2df71ebbae66/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= +sigs.k8s.io/structured-merge-diff/v2 v2.0.1/go.mod h1:Wb7vfKAodbKgf6tn1Kl0VvGj7mRH6DGaRcixXEJXTsE= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= +sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= +sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= diff --git a/pkg/address/address.go b/pkg/address/address.go new file mode 100644 index 0000000..0591076 --- /dev/null +++ b/pkg/address/address.go @@ -0,0 +1,85 @@ +package address + +import ( + "bytes" + "encoding/hex" + "errors" + + "gitlab.33.cn/chat/dtalk/pkg/address/base58" + "gitlab.33.cn/chat/dtalk/pkg/address/crypto" +) + +//Address 地址 +type Address struct { + Version byte + Hash160 [20]byte // For a stealth address: it's HASH160 + Checksum []byte // Unused for a stealth address + Pubkey []byte // Unused for a stealth address + Enc58str string +} + +//SetBytes 设置地址的bytes +func (a *Address) SetBytes(b []byte) { + copy(a.Hash160[:], b) +} + +func (a *Address) String() string { + if a.Enc58str == "" { + var ad [25]byte + ad[0] = a.Version + copy(ad[1:21], a.Hash160[:]) + if a.Checksum == nil { + sh := crypto.Sha2Sum(ad[0:21]) + a.Checksum = make([]byte, 4) + copy(a.Checksum, sh[:4]) + } + copy(ad[21:25], a.Checksum[:]) + a.Enc58str = base58.Encode(ad[:]) + } + return a.Enc58str +} + +//NormalVer 普通地址的版本号 +const NormalVer byte = 0 + +func PublicKeyToAddress(version byte, in []byte) string { + a := new(Address) + a.Pubkey = make([]byte, len(in)) + copy(a.Pubkey[:], in[:]) + a.Version = version + a.SetBytes(crypto.Rimp160(in)) + return a.String() +} + +//CheckAddress 检查地址 +func CheckAddress(ver byte, addr string) (e error) { + dec := base58.Decode(addr) + if dec == nil { + e = errors.New("Cannot decode b58 string '" + addr + "'") + return + } + if len(dec) < 25 { + e = errors.New("Address too short " + hex.EncodeToString(dec)) + return + } + //version 的错误优先 + if dec[0] != ver { + e = ErrCheckVersion + return + } + //需要兼容以前的错误(以前的错误,是一种特殊的情况) + if len(dec) == 25 { + sh := crypto.Sha2Sum(dec[0:21]) + if !bytes.Equal(sh[:4], dec[21:25]) { + e = ErrCheckChecksum + return + } + } + var cksum [4]byte + copy(cksum[:], dec[len(dec)-4:]) + //新的错误: 这个错误用一种新的错误标记 + if crypto.Checksum(dec[:len(dec)-4]) != cksum { + e = ErrAddressChecksum + } + return e +} diff --git a/pkg/address/address_test.go b/pkg/address/address_test.go new file mode 100644 index 0000000..3a832c6 --- /dev/null +++ b/pkg/address/address_test.go @@ -0,0 +1,30 @@ +package address + +import ( + "encoding/hex" + "testing" +) + +func Test_Encoding(t *testing.T) { + in, err := hex.DecodeString("02cec0b297406fc5298e9fe829c9f6f96fa176a1bd1a7c55fa0d345a6f49b09d25") + if err != nil { + t.Error(err) + return + } + addr := PublicKeyToAddress(NormalVer, in) + t.Log(addr) + if err := CheckAddress(NormalVer, addr); err != nil { + t.Error(err) + return + } + t.Log("check success") +} + +func Test_CheckAddress(t *testing.T) { + addr := "1JoFzozbxvst22c2K7MBYwQGjCaMZbC5Qm" + if err := CheckAddress(NormalVer, addr); err != nil { + t.Error(err) + return + } + t.Log("check success") +} diff --git a/pkg/address/base58/alphabet.go b/pkg/address/base58/alphabet.go new file mode 100644 index 0000000..1f225b6 --- /dev/null +++ b/pkg/address/base58/alphabet.go @@ -0,0 +1,50 @@ +// Copyright (c) 2015 The btcsuite developers +// Copyright (c) 2015 The Decred developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +// AUTOGENERATED by genalphabet.go; do not edit. + +package base58 + +const ( + // alphabet is the modified base58 alphabet used by Bitcoin. + alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" + + alphabetIdx0 = '1' +) + +var b58 = [256]byte{ + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 0, 1, 2, 3, 4, 5, 6, + 7, 8, 255, 255, 255, 255, 255, 255, + 255, 9, 10, 11, 12, 13, 14, 15, + 16, 255, 17, 18, 19, 20, 21, 255, + 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 255, 255, 255, 255, 255, + 255, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 255, 44, 45, 46, + 47, 48, 49, 50, 51, 52, 53, 54, + 55, 56, 57, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, +} diff --git a/pkg/address/base58/base58.go b/pkg/address/base58/base58.go new file mode 100644 index 0000000..65139fe --- /dev/null +++ b/pkg/address/base58/base58.go @@ -0,0 +1,118 @@ +// Copyright (c) 2013-2015 The btcsuite developers +// Copyright (c) 2015-2019 The Decred developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package base58 + +//go:generate go run genalphabet.go + +// Decode decodes a modified base58 string to a byte slice. +func Decode(input string) []byte { + if len(input) == 0 { + return []byte("") + } + + // The max possible output size is when a base58 encoding consists of + // nothing but the alphabet character at index 0 which would result in the + // same number of bytes as the number of input chars. + output := make([]byte, len(input)) + + // Encode to base256 in reverse order to avoid extra calculations to + // determine the final output size in favor of just keeping track while + // iterating. + var index int + for _, r := range input { + // Invalid base58 character. + val := uint32(b58[r]) + if val == 255 { + return []byte("") + } + + // Multiply each byte in the output by 58 and encode to base256 while + // propagating the carry. + for i, b := range output[:index] { + val += uint32(b) * 58 + output[i] = byte(val) + val >>= 8 + } + for ; val > 0; val >>= 8 { + output[index] = byte(val) + index++ + } + } + + // Account for the leading zeros in the input. They are appended since the + // encoding is happening in reverse order. + for _, r := range input { + if r != alphabetIdx0 { + break + } + + output[index] = 0 + index++ + } + + // Truncate the output buffer to the actual number of decoded bytes and + // reverse it since it was calculated in reverse order. + output = output[:index:index] + for i := 0; i < index/2; i++ { + output[i], output[index-1-i] = output[index-1-i], output[i] + } + + return output +} + +// Encode encodes a byte slice to a modified base58 string. +func Encode(input []byte) string { + // Since the conversion is from base256 to base58, the max possible number + // of bytes of output per input byte is log_58(256) ~= 1.37. Thus, the max + // total output size is ceil(len(input) * 137/100). Rather than worrying + // about the ceiling, just add one even if it isn't needed since the final + // output is truncated to the right size at the end. + output := make([]byte, (len(input)*137/100)+1) + + // Encode to base58 in reverse order to avoid extra calculations to + // determine the final output size in favor of just keeping track while + // iterating. + var index int + for _, r := range input { + // Multiply each byte in the output by 256 and encode to base58 while + // propagating the carry. + val := uint32(r) + for i, b := range output[:index] { + val += uint32(b) << 8 + output[i] = byte(val % 58) + val /= 58 + } + for ; val > 0; val /= 58 { + output[index] = byte(val % 58) + index++ + } + } + + // Replace the calculated remainders with their corresponding base58 digit. + for i, b := range output[:index] { + output[i] = alphabet[b] + } + + // Account for the leading zeros in the input. They are appended since the + // encoding is happening in reverse order. + for _, r := range input { + if r != 0 { + break + } + + output[index] = alphabetIdx0 + index++ + } + + // Truncate the output buffer to the actual number of encoded bytes and + // reverse it since it was calculated in reverse order. + output = output[:index:index] + for i := 0; i < index/2; i++ { + output[i], output[index-1-i] = output[index-1-i], output[i] + } + + return string(output) +} diff --git a/pkg/address/crypto/hash.go b/pkg/address/crypto/hash.go new file mode 100644 index 0000000..f51195d --- /dev/null +++ b/pkg/address/crypto/hash.go @@ -0,0 +1,44 @@ +package crypto + +import ( + "crypto/sha256" + + "golang.org/x/crypto/ripemd160" +) + +func Checksum(input []byte) (cksum [4]byte) { + h := sha256.Sum256(input) + h2 := sha256.Sum256(h[:]) + copy(cksum[:], h2[:4]) + return +} + +// Sha2Sum Returns hash: SHA256( SHA256( data ) ) +// Where possible, using ShaHash() should be a bit faster +func Sha2Sum(b []byte) []byte { + tmp := sha256.Sum256(b) + tmp = sha256.Sum256(tmp[:]) + return tmp[:] +} + +// Rimp160 Returns hash: RIMP160( SHA256( data ) ) +// Where possible, using RimpHash() should be a bit faster +func Rimp160(b []byte) []byte { + out := make([]byte, 20) + rimpHash(b, out[:]) + return out[:] +} + +func rimpHash(in []byte, out []byte) { + sha := sha256.New() + _, err := sha.Write(in) + if err != nil { + return + } + rim := ripemd160.New() + _, err = rim.Write(sha.Sum(nil)[:]) + if err != nil { + return + } + copy(out, rim.Sum(nil)) +} diff --git a/pkg/address/error.go b/pkg/address/error.go new file mode 100644 index 0000000..ea08184 --- /dev/null +++ b/pkg/address/error.go @@ -0,0 +1,12 @@ +package address + +import "errors" + +// ErrCheckVersion : +var ErrCheckVersion = errors.New("check version error") + +//ErrCheckChecksum : +var ErrCheckChecksum = errors.New("Address Checksum error") + +//ErrAddressChecksum : +var ErrAddressChecksum = errors.New("address checksum error") diff --git a/pkg/api/api.go b/pkg/api/api.go new file mode 100644 index 0000000..c1f27ba --- /dev/null +++ b/pkg/api/api.go @@ -0,0 +1,83 @@ +package api + +import ( + "fmt" + "net/http" + "reflect" + "strconv" + "time" + + "github.com/gin-gonic/gin" + "gopkg.in/go-playground/validator.v8" +) + +// 处理跨域请求,支持options访问 +func Cors() gin.HandlerFunc { + return func(c *gin.Context) { + method := c.Request.Method + c.Header("Access-Control-Allow-Origin", "*") + c.Header("Access-Control-Allow-Headers", "*") //Content-Type,AccessToken,X-CSRF-Token,Authorization,Token,FZM-APP-ID + c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, PATCH, DELETE") + c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type") + c.Header("Access-Control-Allow-Credentials", "true") + + // 放行所有OPTIONS方法,因为有的模板是要请求两次的 + if method == "OPTIONS" { + c.AbortWithStatus(http.StatusNoContent) + } + + // 处理请求 + c.Next() + } +} + +// defaultLogFormatter is the default log format function Logger middleware uses. +var Chat33GinLogFormatter = func(param gin.LogFormatterParams) string { + var statusColor, methodColor, resetColor string + if param.IsOutputColor() { + statusColor = param.StatusCodeColor() + methodColor = param.MethodColor() + resetColor = param.ResetColor() + } + + if param.Latency > time.Minute { + // Truncate in a golang < 1.8 safe way + param.Latency = param.Latency - param.Latency%time.Second + } + return fmt.Sprintf("[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %s\n%s", + param.TimeStamp.Format("2006/01/02 - 15:04:05"), + statusColor, param.StatusCode, resetColor, + param.Latency, + param.ClientIP, + methodColor, param.Method, resetColor, + param.Path, + //param.Keys[DeviceType], param.Keys[Version], param.Keys[AppId], param.Keys[UserId], param.Keys[Uuid], + param.ErrorMessage, + ) +} + +func CheckNumber( + v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value, + field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string, +) bool { + val := field.Interface() + + switch val.(type) { + case int: + return true + case string: + if val.(string) == "" { + return true + } + _, err := strconv.ParseInt(val.(string), 10, 64) + if err != nil { + return false + } + return true + case int64: + return true + default: + //utility_log.Error("func ToInt error unknow type") + return false + } +} diff --git a/pkg/api/const.go b/pkg/api/const.go new file mode 100644 index 0000000..91909a8 --- /dev/null +++ b/pkg/api/const.go @@ -0,0 +1,33 @@ +package api + +import ( + "context" + "time" +) + +const ( + RespMiddleWareDisabled = "RespMiddleWareDisabled" + + ReqError = "error" + ReqResult = "result" +) + +const ( + Address = "address" + Signature = "signature" + DeviceName = "deviceName" + DeviceType = "deviceType" + Uuid = "uuid" + Version = "version" +) + +const HeaderTimeOut = 120 * time.Second + +func NewAddrWithContext(ctx context.Context) string { + addr, ok := ctx.Value(Address).(string) + if !ok { + addr = "" + } + + return addr +} diff --git a/pkg/api/logger/logmiddleware.go b/pkg/api/logger/logmiddleware.go new file mode 100644 index 0000000..4e29d64 --- /dev/null +++ b/pkg/api/logger/logmiddleware.go @@ -0,0 +1,142 @@ +package logger + +import ( + "bytes" + "io" + "io/ioutil" + "net/http" + "net/http/httputil" + "time" + + "gitlab.33.cn/chat/dtalk/pkg/logger" + + "gitlab.33.cn/chat/dtalk/pkg/api" + + "github.com/gin-gonic/gin" + "github.com/rs/zerolog" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" +) + +type Middleware struct { + log zerolog.Logger +} + +func NewMiddleware(log zerolog.Logger) *Middleware { + return &Middleware{ + log: log, + } +} + +func (m *Middleware) Handle() gin.HandlerFunc { + return func(ctx *gin.Context) { + body := dumpRequest(ctx.Request) + + start := time.Now() + + ctx.Next() + + tlog := logger.NewLogWithCtx(ctx, m.log) + + latency := time.Since(start) + if latency > time.Minute { + latency = latency - latency%time.Second + } + reqURI := ctx.Request.RequestURI + if reqURI == "" { + reqURI = ctx.Request.URL.RequestURI() + } + + tlog.Info(). + Str("clientIP", ctx.ClientIP()). + Str("method", ctx.Request.Method). + Str("Path", reqURI). + Dur("span", latency). + Str("|body", body). + Int("status", ctx.Writer.Status()). + Msg("http req") + + err, ok := ctx.Get(api.ReqError) + if ok { + code, msg := parseErr(err) + if code != 0 { + tlog.Error(). + Int("code", code). + Str("msg", msg). + Msg("http err") + } + } + } +} + +func ParseErr(err interface{}) (code int, msg string) { + return parseErr(err) +} + +func parseErr(err interface{}) (code int, msg string) { + if err != nil { + switch ty := err.(type) { + case *xerror.Error: + code = ty.Code() + msg = ty.Error() + case error: + code = xerror.CodeInnerError + msg = err.(error).Error() + default: + e := xerror.NewError(xerror.CodeInnerError) + code = e.Code() + msg = e.Error() + } + return + } + + code = xerror.CodeOK + + return +} + +func DupReadCloser(reader io.ReadCloser) (io.ReadCloser, io.ReadCloser) { + var buf bytes.Buffer + tee := io.TeeReader(reader, &buf) + return ioutil.NopCloser(tee), ioutil.NopCloser(&buf) +} + +// dumpRequest 格式化请求样式 +func dumpRequest(req *http.Request) string { + var dup io.ReadCloser + var err error + //req.Body, dup = iox.DupReadCloser(req.Body) + req.Body, dup = DupReadCloser(req.Body) + + var b bytes.Buffer + + //reqURI := req.RequestURI + //if reqURI == "" { + // reqURI = req.URL.RequestURI() + //} + + //fmt.Fprintf(&b, "%s - %s - HTTP/%d.%d - OperaotrId:%s - ReqBody:", req.Method, + // reqURI, req.ProtoMajor, req.ProtoMinor, operatorId) + + chunked := len(req.TransferEncoding) > 0 && req.TransferEncoding[0] == "chunked" + if req.Body != nil { + var n int64 + var dest io.Writer = &b + if chunked { + dest = httputil.NewChunkedWriter(dest) + } + n, err = io.Copy(dest, req.Body) + if chunked { + dest.(io.Closer).Close() + } + if n > 0 { + //io.WriteString(&b, "\n") + } + } + + req.Body = dup + if err != nil { + return err.Error() + } + + return b.String() +} diff --git a/pkg/api/midware.go b/pkg/api/midware.go new file mode 100644 index 0000000..e4711cf --- /dev/null +++ b/pkg/api/midware.go @@ -0,0 +1,192 @@ +package api + +import ( + "crypto/sha256" + "encoding/base64" + "fmt" + "net/http" + "reflect" + "strconv" + "strings" + + "github.com/gin-gonic/gin" + zlog "github.com/rs/zerolog/log" + "gitlab.33.cn/chat/dtalk/pkg/address" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/pkg/util" +) + +var log = zlog.Logger + +func composeHttpResp(code int, msg string, data interface{}) interface{} { + type HttpAck struct { + Result int `json:"result"` + Message string `json:"message"` + Data interface{} `json:"data"` + } + var ret HttpAck + ret.Result = code + ret.Message = msg + ret.Data = data + return &ret +} + +func parseRlt(result interface{}, err interface{}) (code int, msg string, data interface{}) { + if err != nil { + switch ty := err.(type) { + case *xerror.Error: + code = ty.Code() + msg = ty.Error() + data = ty.Data() + default: + log.Warn().Interface("err", err).Msg("inner error type") + e := xerror.NewError(xerror.CodeInnerError) + code = e.Code() + msg = e.Error() + data = err + } + return + } + + code = xerror.CodeOK + if isNil(result) { + data = gin.H{} + } else { + data = result + } + return +} + +func isNil(i interface{}) bool { + vi := reflect.ValueOf(i) + if vi.Kind() == reflect.Ptr { + return vi.IsNil() + } + return false +} + +func RespMiddleWare() gin.HandlerFunc { + return func(c *gin.Context) { + c.Next() + if v, ok := c.Get(RespMiddleWareDisabled); ok && v == true { + return + } + err := c.MustGet(ReqError) + result, _ := c.Get(ReqResult) + ret := composeHttpResp(parseRlt(result, err)) + c.PureJSON(http.StatusOK, ret) + //c.PureJSON() + } +} + +func AuthMiddleWare() gin.HandlerFunc { + return func(context *gin.Context) { + sig := context.GetHeader("FZM-SIGNATURE") + uuid := context.GetHeader("FZM-UUID") + device := context.GetHeader("FZM-DEVICE") + deviceName := context.GetHeader("FZM-DEVICE-NAME") + version := context.GetHeader("FZM-VERSION") + + // + if sig == "MOCK" || sig == "MOCK2" { + mockAddr := "" + switch sig { + case "MOCK": + mockAddr = "1FKxgaEh5fuSm7a35BfUnKYAmradowpiTR" + case "MOCK2": + mockAddr = "1AsPsahP7FvpR7F2de1LhSB4SU5ShqZ7eu" + } + //set val + context.Set(Signature, sig) + context.Set(Address, mockAddr) + context.Set(Uuid, uuid) + context.Set(DeviceType, device) + context.Set(DeviceName, deviceName) + context.Set(Version, version) + } else { + pubKey, err := VerifyAddress(sig) + if err != nil { + log.Debug().Err(err).Msg("VerifyAddress failed") + context.Set(ReqError, err) + context.Abort() + return + } + addr := address.PublicKeyToAddress(address.NormalVer, pubKey) + if addr == "" { + log.Debug().Msg("PublicKeyToAddress addr is empty") + context.Set(ReqError, err) + context.Abort() + return + } + //set val + context.Set(Signature, sig) + context.Set(Address, addr) + context.Set(Uuid, uuid) + context.Set(DeviceType, device) + context.Set(DeviceName, deviceName) + context.Set(Version, version) + } + } +} + +func HeaderMiddleWare() gin.HandlerFunc { + return func(context *gin.Context) { + uuid := context.GetHeader("FZM-UUID") + device := context.GetHeader("FZM-DEVICE") + deviceName := context.GetHeader("FZM-DEVICE-NAME") + version := context.GetHeader("FZM-VERSION") + + //set val + context.Set(Uuid, uuid) + context.Set(DeviceType, device) + context.Set(DeviceName, deviceName) + context.Set(Version, version) + } +} + +//get pubKey +func VerifyAddress(str string) ([]byte, error) { + //##
; <> + ss := strings.SplitN(str, "#", -1) + if len(ss) < 3 { + log.Debug().Err(fmt.Errorf("need length:%v,got:%v", 3, len(ss))).Str("sig", str).Msg("split signature failed") + return nil, xerror.NewError(xerror.SignatureInvalid) + } + sigData := ss[0] + msgData := ss[1] + pubKeyData := ss[2] + + msg := strings.SplitN(msgData, "*", -1) + if len(msg) < 2 { + log.Debug().Err(fmt.Errorf("need msg length:%v,got:%v", 2, len(ss))).Str("msgData", msgData).Msg("split msg data failed") + return nil, xerror.NewError(xerror.SignatureInvalid) + } + time, err := strconv.ParseInt(msg[0], 10, 64) + if err != nil { + log.Debug().Err(err).Str("datetime", msg[0]).Msg("ParseInt datetime failed") + return nil, xerror.NewError(xerror.SignatureInvalid) + } + //secp256 + sig, err := base64.StdEncoding.DecodeString(sigData) + if err != nil { + log.Debug().Err(err).Str("sigData", sigData).Msg("base64 decode sig data failed") + return nil, xerror.NewError(xerror.SignatureInvalid) + } + pubKey, err := util.HexDecode(pubKeyData) + if err != nil { + log.Debug().Err(err).Str("pubKeyData", pubKeyData).Msg("hex decode pubKey failed") + return nil, xerror.NewError(xerror.SignatureInvalid) + } + msg256 := sha256.Sum256([]byte(msgData)) + if !util.Secp256k1Verify(msg256[:], sig, pubKey) { + log.Debug().Err(err).Str("msgData", msgData).Bytes("sig", sig).Bytes("pubKey", pubKey). + Msg("Secp256k1Verify failed") + return nil, xerror.NewError(xerror.SignatureInvalid) + } + //检查时间是否过期 + if util.CheckTimeOut(time, HeaderTimeOut) { + log.Debug().Err(err).Int64("time", time).Msg("verify timeout") + return nil, xerror.NewError(xerror.SignatureExpired) + } + return pubKey, nil +} diff --git a/pkg/api/midware_test.go b/pkg/api/midware_test.go new file mode 100644 index 0000000..3e3ed92 --- /dev/null +++ b/pkg/api/midware_test.go @@ -0,0 +1,15 @@ +package api + +import "testing" + +func Test_VerifyAddress(t *testing.T) { + //sig := "5a3tPpKtUwVzOXAmEFuMRNdHJqJnkjbWUPKVYJDLJRV9+AksajpvT9UUSeNFVVL1W1F8EUDQt01bp11jtV8gbwA=#1610336258730*6XofpoSc#0375610055c57e011a0a51457e0ce451849a4ca588b0ff0beb0ba5d929ca2dd82b" + sig := "sjL53g5zbfwREjuBDfTpyaQRfULgip2Ax0Es3tVEIuBCxvWryXm7EVRv/jEmmi6ZlMZZbEXeKlBxrFt41OCPWAE=#1626170987818458*2syd7kfaiw#036801a786cba366d5f62283173681dd5801740d3ef6fe1d4383680f3c0f8e7d4f" + + b, err := VerifyAddress(sig) + if err != nil { + t.Errorf("verify failed: %v", err) + return + } + t.Logf("verify:%v", b) +} diff --git a/pkg/api/prometheus/prometheus.go b/pkg/api/prometheus/prometheus.go new file mode 100644 index 0000000..fc2cc22 --- /dev/null +++ b/pkg/api/prometheus/prometheus.go @@ -0,0 +1,88 @@ +package prometheus + +import ( + "github.com/gin-gonic/gin" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + "github.com/prometheus/client_golang/prometheus/promhttp" + "reflect" +) + +func PrometheusHandler() gin.HandlerFunc { + h := promhttp.Handler() + + return func(c *gin.Context) { + h.ServeHTTP(c.Writer, c.Request) + } +} + +func isNil(i interface{}) bool { + vi := reflect.ValueOf(i) + if vi.Kind() == reflect.Ptr { + return vi.IsNil() + } + return false +} + +func PrometheusMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + //path := c.FullPath() + //timer := prometheus.NewTimer(httpDuration.WithLabelValues(path)) + //c.Next() + // + //// 统计错误 code + //err := c.MustGet(api.ReqError) + //code, _, _ := parseErr(nil, err) + ////if code != 0 { + //// responseCode.WithLabelValues(strconv.Itoa(code)).Inc() + ////} + // + //status := c.Writer.Status() + ////responseStatus.WithLabelValues(strconv.Itoa(status)).Inc() + //totalRequests.WithLabelValues(path, strconv.Itoa(status), strconv.Itoa(code)).Inc() + //timer.ObserveDuration() + } +} + +func init() { + _ = prometheus.Register(totalRequests) + //_ = prometheus.Register(responseStatus) + _ = prometheus.Register(httpDuration) + //_ = prometheus.Register(responseCode) +} + +// 统计所有 url 访问次数 +var totalRequests = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "http_requests_total", + Help: "Number of get requests.", + }, + []string{"path", "status", "code"}, +) + +// +//var responseStatus = prometheus.NewCounterVec( +// prometheus.CounterOpts{ +// Name: "http_response_status", +// Help: "Status of HTTP response", +// }, +// []string{"status"}, +//) +// +//// +//var responseCode = prometheus.NewCounterVec( +// prometheus.CounterOpts{ +// Name: "http_response_code", +// Help: "Status of HTTP response", +// }, +// []string{"code"}, +//) + +// +var httpDuration = promauto.NewHistogramVec( + prometheus.HistogramOpts{ + Name: "http_response_time_seconds", + Help: "Duration of HTTP requests.", + }, + []string{"path"}, +) diff --git a/pkg/api/trace/tracemiddleware.go b/pkg/api/trace/tracemiddleware.go new file mode 100644 index 0000000..fec6032 --- /dev/null +++ b/pkg/api/trace/tracemiddleware.go @@ -0,0 +1,28 @@ +package trace + +import ( + "context" + + "github.com/gin-gonic/gin" + "github.com/rs/xid" +) + +const DtalkTraceId = "X-dtalk-tracd-id" + +func NewTraceIdWithContext(ctx context.Context) string { + logId, ok := ctx.Value(DtalkTraceId).(string) + if !ok { + logId = xid.New().String() + } + + return logId +} + +func TraceMiddleware() gin.HandlerFunc { + return func(ctx *gin.Context) { + // 或者从 header 中拿 + + ctx.Set(DtalkTraceId, NewTraceIdWithContext(ctx)) + ctx.Next() + } +} diff --git a/pkg/config/config.go b/pkg/config/config.go new file mode 100644 index 0000000..00e32f1 --- /dev/null +++ b/pkg/config/config.go @@ -0,0 +1,72 @@ +package config + +import ( + "fmt" + "github.com/BurntSushi/toml" + "io/ioutil" + "os" + "path" + "syscall" +) + +var loaders = map[string]func([]byte, interface{}) error{ + //".json": LoadConfigFromJsonBytes, + //".yaml": LoadConfigFromYamlBytes, + //".yml": LoadConfigFromYamlBytes, + ".toml": LoadConfigFromTomlBytes, +} + +// LoadConfig loads config into v from file, .json, .yaml and .yml are acceptable. +func LoadConfig(file string, v interface{}, opts ...Option) error { + content, err := ioutil.ReadFile(file) + if err != nil { + return err + } + + loader, ok := loaders[path.Ext(file)] + if !ok { + return fmt.Errorf("unrecognized file type: %s", file) + } + + var opt options + for _, o := range opts { + o(&opt) + } + + if opt.env { + return loader([]byte(os.ExpandEnv(string(content))), v) + } + + return loader(content, v) +} + +//// LoadConfigFromJsonBytes loads config into v from content json bytes. +//func LoadConfigFromJsonBytes(content []byte, v interface{}) error { +// return mapping.UnmarshalJsonBytes(content, v) +//} +// +//// LoadConfigFromYamlBytes loads config into v from content yaml bytes. +//func LoadConfigFromYamlBytes(content []byte, v interface{}) error { +// return mapping.UnmarshalYamlBytes(content, v) +//} +// +//// MustLoad loads config into v from path, exits on error. +//func MustLoad(path string, v interface{}, opts ...Option) { +// if err := LoadConfig(path, v, opts...); err != nil { +// log.Fatalf("error: config file %s, %s", path, err.Error()) +// } +//} + +// LoadConfigFromTomlBytes loads config into v from content toml bytes. +func LoadConfigFromTomlBytes(content []byte, v interface{}) error { + _, err := toml.Decode(string(content), v) + return err +} + +func GetEnv(key string, defaultVal string) string { + val, ok := syscall.Getenv(key) + if !ok { + return defaultVal + } + return val +} diff --git a/pkg/config/options.go b/pkg/config/options.go new file mode 100644 index 0000000..54026e8 --- /dev/null +++ b/pkg/config/options.go @@ -0,0 +1,23 @@ +package config + +type ( + // Option defines the method to customize the config options. + Option func(opt *options) + + options struct { + env bool + } +) + +// UseEnv customizes the config to use environment variables. +func UseEnv() Option { + return func(opt *options) { + opt.env = true + } +} + +func SetEnv(f bool) Option { + return func(opt *options) { + opt.env = f + } +} diff --git a/pkg/contextx/valueonlycontext.go b/pkg/contextx/valueonlycontext.go new file mode 100644 index 0000000..40170b2 --- /dev/null +++ b/pkg/contextx/valueonlycontext.go @@ -0,0 +1,29 @@ +package contextx + +import ( + "context" + "time" +) + +type valueOnlyContext struct { + context.Context +} + +func (valueOnlyContext) Deadline() (deadline time.Time, ok bool) { + return +} + +func (valueOnlyContext) Done() <-chan struct{} { + return nil +} + +func (valueOnlyContext) Err() error { + return nil +} + +// ValueOnlyFrom takes all values from the given ctx, without deadline and error control. +func ValueOnlyFrom(ctx context.Context) context.Context { + return valueOnlyContext{ + Context: ctx, + } +} diff --git a/pkg/error/code.go b/pkg/error/code.go new file mode 100644 index 0000000..265eec9 --- /dev/null +++ b/pkg/error/code.go @@ -0,0 +1,136 @@ +package error + +const ( + CodeOK = 0 + CodeInnerError = -1000 + QueryFailed = -1001 + ExecFailed = -1002 + SignatureInvalid = -1010 + SignatureExpired = -1011 + ParamsError = -1012 + RPCFailed = -1013 + TokenError = -1014 + DeviceTypeError = -1015 + + FeaturesUnSupported = -2000 + VerifyCodeSendError = -4006 + VerifyCodeError = -4007 + VerifyCodeExpired = -4008 + QueryNotExist = -4009 + SendMsgFailed = -5001 + + ExportAddressPhoneInconsistent = -4010 + ExportAddressEmailInconsistent = -4011 + + GroupCreateFailed = -10000 + GroupStatusBlock = -10001 + GroupStatusDisBand = -10002 + GroupMemberLimit = -10003 + GroupInviteMemberFailed = -10004 + GroupInvitePermissionDenied = -10005 + GroupMemberTypeOther = -10006 + GroupExit = -10007 + GroupRemove = -10008 + GroupDisband = -10009 + GroupInviteNoMembers = -10010 + GroupRemoveNoMembers = -10011 + GroupAdminDeny = -10012 + GroupOwnerDeny = -10013 + GroupChangeOwner = -10014 + GroupPersonNotExist = -10015 + GroupMemberNotExist = -10016 + GroupChangeOwnerSelf = -10017 + GroupSetAdmin = -10018 + GroupAdminNumLimit = -10019 + GroupNotExist = -10020 + GroupHigherPermission = -10021 + GroupOwnerExit = -10022 + GroupInviteMemberExist = -10023 + GroupOwnerDisband = -10024 + GroupOwnerChange = -10025 + GroupOwnerSetAdmin = -10026 + GroupMutePermission = -10027 + GroupApplyUsed = -10028 + GroupMemberExist = -10029 + GroupApplyNotExist = -10030 + + CallUserBusy = -10101 + + OssFileTooSmall = -10201 + OssFileTooBig = -10202 + OssKeyIllegal = -10203 + + CdkOutOfStock = -10301 + CdkOrderError = -10302 + CdkStatusNotFrozen = -10303 + CdkCoinNameErr = -10304 + CdkCoinNameExist = -10305 + CdkMaxNumberErr = -10306 +) + +var errorMsg = map[int]string{ + CodeOK: "操作成功", + CodeInnerError: "访问失败", + QueryFailed: "查询失败", + ExecFailed: "修改失败", + SignatureInvalid: "无效签名", + SignatureExpired: "签名过期", + ParamsError: "请求参数错误", + RPCFailed: "调用失败", + VerifyCodeError: "验证失败", + VerifyCodeExpired: "验证码已经过期或者已使用", + VerifyCodeSendError: "验证码发送失败", + QueryNotExist: "该手机号或邮箱不存在", + SendMsgFailed: "消息发送失败", + FeaturesUnSupported: "未支持该功能", + TokenError: "token错误", + DeviceTypeError: "获取设备类型失败", + + ExportAddressPhoneInconsistent: "账号与绑定手机不一致", + ExportAddressEmailInconsistent: "账号与绑定邮箱不一致", + + GroupCreateFailed: "群创建失败", + GroupStatusBlock: "群正在被封禁中", + GroupStatusDisBand: "群已被解散", + GroupMemberLimit: "超出群人数上限", + GroupInviteMemberFailed: "邀请群成员失败", + GroupInvitePermissionDenied: "邀请群成员权限不足", + GroupMemberTypeOther: "你已不在本群中", + GroupExit: "退群失败", + GroupRemove: "踢人失败", + GroupDisband: "解散群失败", + GroupInviteNoMembers: "被邀请人已经都在本群中", + GroupRemoveNoMembers: "没有可以踢出群的人", + GroupAdminDeny: "需要管理员权限", + GroupOwnerDeny: "需要群主权限", + GroupChangeOwner: "转让群主失败", + GroupPersonNotExist: "你已不在本群中", + GroupMemberNotExist: "对方不在本群中", + GroupChangeOwnerSelf: "不能把群转让给自己", + GroupSetAdmin: "设置管理员失败", + GroupAdminNumLimit: "管理员数量已满", + GroupHigherPermission: "需要更高权限", + GroupOwnerExit: "群主不能主动退群", + GroupInviteMemberExist: "被邀请加入的用户已经是群成员", + GroupOwnerDisband: "只有群主可以解散群", + GroupOwnerChange: "只有群主可以转让群", + GroupOwnerSetAdmin: "只有群主可以设置管理员", + GroupNotExist: "该群号不存在", + GroupMutePermission: "群主或管理员不能被禁言", + GroupApplyUsed: "该申请已处理", + GroupMemberExist: "你已在本群中", + GroupApplyNotExist: "该申请不存在", + + CallUserBusy: "对方在忙,请稍后再试", + + OssFileTooSmall: "上传分片文件太小", + OssFileTooBig: "上传文件太大", + OssKeyIllegal: "文件路径非法", + + CdkOutOfStock: "兑换码数量不足", + CdkOrderError: "订单号错误", + CdkStatusNotFrozen: "兑换码状态错误", + CdkCoinNameErr: "该票券暂时不支持兑换", + CdkCoinNameExist: "同名票券已存在", + CdkMaxNumberErr: "该优惠券已达兑换上限, 请兑换其他优惠券", +} diff --git a/pkg/error/error.go b/pkg/error/error.go new file mode 100644 index 0000000..64dee52 --- /dev/null +++ b/pkg/error/error.go @@ -0,0 +1,100 @@ +package error + +import ( + "fmt" + "strings" + + "github.com/rs/zerolog/log" +) + +const ( + DispMsgExt = 0 + DispJustExt = 1 +) + +//错误信息组合 +//1. message + : + extMsg +//2. extMsg +func NewError(code int) *Error { + return &Error{ + code: code, + data: make(map[string]interface{}), + } +} + +type Error struct { + code int + //额外错误信息 + extMsg string + //暴露给接口,但客户端不显示 + data map[string]interface{} + //显示方式 + displayType int +} + +func (e *Error) Code() int { + return e.code +} + +func (e *Error) Data() map[string]interface{} { + return e.data +} + +//策略返回显示的错误信息 +func (e *Error) Error() string { + switch e.displayType { + case DispJustExt: + return e.extMsg + default: + return strings.TrimRight(fmt.Sprintf("%s%s%s", errMsg(e.code), ":", e.extMsg), `:`) + } +} + +//external +func (e *Error) JustShowExtMsg() *Error { + e.displayType = DispJustExt + return e +} + +func (e *Error) SetExtMessage(extMsg string) *Error { + e.extMsg = extMsg + return e +} + +//data["service"]=service name +//data["code"]=code +//data["message"]=message +func (e *Error) SetChildErr(name string, code interface{}, message interface{}) *Error { + if e.data == nil { + e.data = make(map[string]interface{}) + } + e.data["service"] = name + e.WriteCode(code) + e.WriteMessage(message) + return e +} + +func (e *Error) WriteMessage(msg interface{}) *Error { + if e.data == nil { + e.data = make(map[string]interface{}) + } + e.data["message"] = msg + return e +} + +func (e *Error) WriteCode(code interface{}) *Error { + if e.data == nil { + e.data = make(map[string]interface{}) + } + e.data["code"] = code + return e +} + +func errMsg(code int) string { + errStr, ok := errorMsg[code] + if !ok { + log.Error().Err(fmt.Errorf("error code not find")) + return "" + } + return errStr +} diff --git a/pkg/error/interceptor.go b/pkg/error/interceptor.go new file mode 100644 index 0000000..5ba5e2e --- /dev/null +++ b/pkg/error/interceptor.go @@ -0,0 +1,52 @@ +package error + +import ( + "context" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// WrapErr 返回gRPC状态码包装后的业务错误 +func WrapErr(err error) error { + if err == nil { + return nil + } + + switch e := err.(type) { + case interface{ GRPCStatus() *status.Status }: + return e.GRPCStatus().Err() + case *Error: + return status.Error(codes.Code(-e.Code()), e.Error()) + default: + return status.Error(codes.Unknown, err.Error()) + } +} + +// ErrInterceptor 业务错误服务端一元拦截器 +func ErrInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + resp, err := handler(ctx, req) + return resp, WrapErr(err) +} + +// ErrClientInterceptor 业务错误客户端一元拦截器 +func ErrClientInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { + err := invoker(ctx, method, req, reply, cc, opts...) + return UnwrapErr(err) +} + +// UnwrapErr 返回gRPC状态码解包后的业务错误 +func UnwrapErr(err error) error { + if err == nil { + return nil + } + + s, _ := status.FromError(err) + c := int(s.Code()) + + if _, ok := errorMsg[-c]; ok { + return NewError(-c) + } + + return err +} diff --git a/pkg/interceptor/logger/server.go b/pkg/interceptor/logger/server.go new file mode 100644 index 0000000..86e426c --- /dev/null +++ b/pkg/interceptor/logger/server.go @@ -0,0 +1,61 @@ +package logger + +import ( + "context" + "strings" + + api "gitlab.33.cn/chat/dtalk/pkg/api/logger" + + "gitlab.33.cn/chat/dtalk/pkg/logger" + + "github.com/rs/zerolog" + "google.golang.org/grpc" +) + +type Logger interface { + Info(msg string, ctx ...interface{}) + Error(msg string, ctx ...interface{}) +} + +type ServerInterceptor struct { + log zerolog.Logger + filter []string +} + +func NewServerInterceptor(log zerolog.Logger, filter []string) *ServerInterceptor { + return &ServerInterceptor{ + log: log, + filter: filter, + } +} + +func (s *ServerInterceptor) Unary(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + //log.Infof("%s req: %v", info.FullMethod, req) + for _, method := range s.filter { + if strings.HasPrefix(info.FullMethod, method) { + return handler(ctx, req) + } + } + + logt := logger.NewLogWithCtx(ctx, s.log) + + logt.Info(). + Str("Path", info.FullMethod). + Interface("|body", req). + Msg("rpc req") + + m, err := handler(ctx, req) + + if err != nil { + //log.Error("%s err: %v", info.FullMethod, err) + code, msg := api.ParseErr(err) + if code != 0 { + logt.Error(). + Int("code", code). + Str("msg", msg). + Msg("rpc err") + } + } + //log.Info("%s resp: %v", info.FullMethod, m) + return m, err +} diff --git a/pkg/interceptor/trace/trace.go b/pkg/interceptor/trace/trace.go new file mode 100644 index 0000000..53e5485 --- /dev/null +++ b/pkg/interceptor/trace/trace.go @@ -0,0 +1,98 @@ +package trace + +import ( + "context" + "gitlab.33.cn/chat/dtalk/pkg/api" + "gitlab.33.cn/chat/dtalk/pkg/api/trace" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" +) + +const ( + // todo rename, oa那里也要一起改 X-Gateway-* + OAOpeId string = "X-OA-Ope-Id" + OATraceId string = "X-OA-Trace-Id" +) + +// NewTraceIdFromMD 从grpc metadata获取traceId +func NewTraceIdFromMD(md metadata.MD) (string, bool) { + if md == nil { + return "", false + } + + var t string + + if traceId := md.Get(OATraceId); len(traceId) > 0 { + t = traceId[0] + } else { + return "", false + } + + return t, true +} + +// NewAddressFromMD 从grpc metadata获取操作者 ID +func NewAddressFromMD(md metadata.MD) (string, bool) { + if md == nil { + return "", false + } + + var t string + + if opeId := md.Get(OAOpeId); len(opeId) > 0 { + t = opeId[0] + } else { + return "", false + } + + return t, true +} + +// ServerUnaryInterceptor 默认令牌服务端一元拦截器 +func ServerUnaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + return handler(wrapServerContext(ctx), req) +} + +// wrapServerContext 包装服务端上下文 +func wrapServerContext(ctx context.Context) context.Context { + md, ok := metadata.FromIncomingContext(ctx) + if !ok { + return ctx + } + + token, ok := NewTraceIdFromMD(md) + if !ok { + // 如果没有就自己创一个 ID + ctx = context.WithValue(ctx, trace.DtalkTraceId, trace.NewTraceIdWithContext(ctx)) + } else { + ctx = context.WithValue(ctx, trace.DtalkTraceId, token) + } + + address, ok := NewAddressFromMD(md) + if !ok { + return ctx + } + ctx = context.WithValue(ctx, api.Address, address) + + return ctx +} + +// UnaryClientInterceptor 默认令牌客户端一元拦截器 +func UnaryClientInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { + return invoker(wrapClientContext(ctx), method, req, reply, cc, opts...) +} + +// wrapClientContext 包装客户端上下文 +func wrapClientContext(ctx context.Context) context.Context { + t, ok := ctx.Value(trace.DtalkTraceId).(string) + if ok { + ctx = metadata.AppendToOutgoingContext(ctx, OATraceId, t) + } + + token := api.NewAddrWithContext(ctx) + if ok { + ctx = metadata.AppendToOutgoingContext(ctx, OAOpeId, token) + } + + return ctx +} diff --git a/pkg/log/log.go b/pkg/log/log.go new file mode 100644 index 0000000..14769bb --- /dev/null +++ b/pkg/log/log.go @@ -0,0 +1,88 @@ +package log + +import ( + "os" + + "github.com/rs/zerolog" +) + +const ( + DebugLevel Level = "debug" + ReleaseLevel Level = "release" + BenchmarkLevel Level = "benchmark" +) + +const ( + DefaultDisplay Display = "json" + JsonDisplay Display = "json" + ConsoleDisplay Display = "console" +) + +const ( + FileMode Mode = "file" + ConsoleMode Mode = "console" +) + +type Level string + +type Display string + +type Mode string + +type Config struct { + Level Level + Mode Mode + Path string + Display Display +} + +func Init(cfg Config) (zerolog.Logger, error) { + var logger = zerolog.New(os.Stdout).With().Timestamp().Logger() + switch cfg.Display { + case ConsoleDisplay: + logger = logger.Output(zerolog.ConsoleWriter{Out: os.Stdout}) + default: + } + + switch cfg.Mode { + case ConsoleMode: + case FileMode: + fp, err := createLogFile(cfg.Path) + if err != nil { + return logger, err + } + logger = logger.Output(fp) + default: + } + + //set log level + switch cfg.Level { + case DebugLevel: + zerolog.SetGlobalLevel(zerolog.DebugLevel) + case ReleaseLevel: + zerolog.SetGlobalLevel(zerolog.InfoLevel) + case BenchmarkLevel: + zerolog.SetGlobalLevel(zerolog.Disabled) + default: + zerolog.SetGlobalLevel(zerolog.InfoLevel) + } + return logger, nil +} + +var fds = make([]*os.File, 0) + +func createLogFile(path string) (*os.File, error) { + f, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_RDWR, os.ModePerm) + if err != nil { + return nil, err + } + //defer f.Close() + fds = append(fds, f) + return f, nil +} + +func Close() { + for _, fd := range fds { + fd.Close() + } +} diff --git a/pkg/logger/const.go b/pkg/logger/const.go new file mode 100644 index 0000000..b7f3333 --- /dev/null +++ b/pkg/logger/const.go @@ -0,0 +1,7 @@ +package logger + +var ( + DEBUG = "debug" + RELEASE = "release" + BENCHMARK = "benchmark" +) diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go new file mode 100644 index 0000000..2e91057 --- /dev/null +++ b/pkg/logger/logger.go @@ -0,0 +1,38 @@ +package logger + +import ( + "context" + "io" + "os" + "time" + + "gitlab.33.cn/chat/dtalk/pkg/api" + "gitlab.33.cn/chat/dtalk/pkg/api/trace" + + "github.com/rs/zerolog" +) + +func New(env, srvName string) zerolog.Logger { + var logger zerolog.Logger + var out io.Writer + out = os.Stdout + //log init + zerolog.SetGlobalLevel(zerolog.InfoLevel) + if env == DEBUG { + out = zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: time.RFC3339} + zerolog.SetGlobalLevel(zerolog.DebugLevel) + } else if env == BENCHMARK { + zerolog.SetGlobalLevel(zerolog.Disabled) + } + logger = zerolog.New(out).With().Timestamp().Str("srvName", srvName).Logger() + + return logger +} + +func NewLogWithCtx(ctx context.Context, log zerolog.Logger) zerolog.Logger { + addr := api.NewAddrWithContext(ctx) + + traceId := trace.NewTraceIdWithContext(ctx) + + return log.With().Str("opeId", addr).Str("trace", traceId).Logger() +} diff --git a/pkg/mysql/mysql.go b/pkg/mysql/mysql.go new file mode 100644 index 0000000..8bc2944 --- /dev/null +++ b/pkg/mysql/mysql.go @@ -0,0 +1,220 @@ +package mysql + +import ( + "database/sql" + "fmt" + + _ "github.com/go-sql-driver/mysql" +) + +type MysqlConn struct { + Host string + Port string + User string + Pwd string + DbName string + + db *sql.DB +} + +// If you do not want to preselect a database, leave dbName empty +// thus, caller need select database before operation on table +func NewMysqlConn(host, port, user, pwd, dbName string, ext ...string) (*MysqlConn, error) { + conn := &MysqlConn{ + Host: host, + Port: port, + User: user, + Pwd: pwd, + DbName: dbName, + } + + charSet := "UTF8" + if len(ext) > 0 { + charSet = ext[0] + } + + dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=%s", user, pwd, host, port, dbName, charSet) + db, err := sql.Open("mysql", dsn) + if err != nil { + return nil, err + } + + err = db.Ping() + if err != nil { + db.Close() + return nil, err + } + + conn.db = db + return conn, nil +} + +func (conn *MysqlConn) Close() error { + return conn.db.Close() +} + +func (conn *MysqlConn) Query(query string, args ...interface{}) ([]map[string]string, error) { + rows, err := conn.db.Query(query, args...) + if err != nil { + return nil, err + } + + defer rows.Close() + return rowsToRecords(rows) +} + +func (conn *MysqlConn) Exec(query string, args ...interface{}) (int64, int64, error) { + res, err := conn.db.Exec(query, args...) + if err != nil { + return 0, 0, err + } + + num, err := res.RowsAffected() + if err != nil { + return 0, 0, err + } + + lastId, err := res.LastInsertId() + if err != nil { + return 0, 0, err + } + return num, lastId, nil +} + +func (conn *MysqlConn) NewTx() (*MysqlTx, error) { + tx, err := conn.db.Begin() + if err != nil { + return nil, err + } + + return &MysqlTx{Tx: tx}, nil +} + +func (conn *MysqlConn) PrepareExec(query string, args ...interface{}) (int64, int64, error) { + //prepare the statement + stmt, err := conn.db.Prepare(query) + if err != nil { + return 0, 0, err + } + //format all vals at once + res, err := stmt.Exec(args...) + if err != nil { + return 0, 0, err + } + + num, err := res.RowsAffected() + if err != nil { + return 0, 0, err + } + + lastId, err := res.LastInsertId() + if err != nil { + return 0, 0, err + } + return num, lastId, nil +} + +// transaction + +type MysqlTx struct { + Tx *sql.Tx +} + +func (tx *MysqlTx) Exec(query string, args ...interface{}) (num int64, lastId int64, err error) { + res, err := tx.Tx.Exec(query, args...) + if err != nil { + return 0, 0, err + } + + num, err = res.RowsAffected() + if err != nil { + tx.Tx.Rollback() + return 0, 0, err + } + + lastId, err = res.LastInsertId() + if err != nil { + tx.Tx.Rollback() + return 0, 0, err + } + + return num, lastId, nil +} + +func (tx *MysqlTx) Query(query string, args ...interface{}) ([]map[string]string, error) { + rows, err := tx.Tx.Query(query, args...) + if err != nil { + tx.Tx.Rollback() + return nil, err + } + + defer rows.Close() + return rowsToRecords(rows) +} + +func (tx *MysqlTx) Commit() error { + return tx.Tx.Commit() +} + +func (tx *MysqlTx) RollBack() { + tx.Tx.Rollback() +} + +func (tx *MysqlTx) PrepareExec(query string, args ...interface{}) (int64, int64, error) { + //prepare the statement + stmt, err := tx.Tx.Prepare(query) + if err != nil { + return 0, 0, err + } + //format all vals at once + res, err := stmt.Exec(args...) + if err != nil { + return 0, 0, err + } + + num, err := res.RowsAffected() + if err != nil { + return 0, 0, err + } + + lastId, err := res.LastInsertId() + if err != nil { + return 0, 0, err + } + return num, lastId, nil +} + +// util + +func rowsToRecords(rows *sql.Rows) ([]map[string]string, error) { + columns, err := rows.Columns() + if err != nil { + return nil, err + } + + values := make([]sql.RawBytes, len(columns)) + scanArgs := make([]interface{}, len(columns)) + for i := range values { + scanArgs[i] = &values[i] + } + + records := []map[string]string{} + for rows.Next() { + if err := rows.Scan(scanArgs...); err != nil { + return nil, err + } + + one := make(map[string]string) + for i, col := range values { + if col != nil { + one[columns[i]] = string(col) + } + } + records = append(records, one) + } + + if err := rows.Err(); err != nil { + return nil, err + } + return records, nil +} diff --git a/pkg/naming/register.go b/pkg/naming/register.go new file mode 100644 index 0000000..7642528 --- /dev/null +++ b/pkg/naming/register.go @@ -0,0 +1,89 @@ +package naming + +import ( + "context" + "fmt" + "log" + "strings" + "time" + + "go.etcd.io/etcd/client/v3" +) + +func Register(etcdAddr, name, addr, schema string, ttl int64) error { + var err error + if cli == nil { + cli, err = clientv3.New(clientv3.Config{ + Endpoints: strings.Split(etcdAddr, ";"), + DialTimeout: 5 * time.Second, + }) + if err != nil { + log.Printf("connect to etcd err:%s", err) + return err + } + } + + ticker := time.NewTicker(time.Second * time.Duration(ttl)) + + go func() { + for { + getResp, err := cli.Get(context.Background(), "/"+schema+"/"+name+"/"+addr) + if err != nil { + fmt.Println("etcd get err:", err) + } else if getResp.Count == 0 { + err = withAlive(name, addr, schema, ttl) + if err != nil { + log.Printf("keep alive:%s", err) + } + } + <-ticker.C + } + }() + + return nil +} + +func withAlive(name, addr, schema string, ttl int64) error { + leaseResp, err := cli.Grant(context.Background(), ttl) + if err != nil { + return err + } + + log.Printf("key:%v\n", "/"+schema+"/"+name+"/"+addr) + _, err = cli.Put(context.Background(), "/"+schema+"/"+name+"/"+addr, addr, clientv3.WithLease(leaseResp.ID)) + if err != nil { + log.Printf("put etcd error:%s", err) + return err + } + + ch, err := cli.KeepAlive(context.Background(), leaseResp.ID) + if err != nil { + log.Printf("keep alive error:%s", err) + return err + } + + go func() { + for { + <-ch + //log.Println("ttl: ", ka.TTL, "ID: ", ka.ID) + } + + //for leaseKeepResp := range ch { + // log.Println("续约成功", leaseKeepResp.ID) + //} + // + //log.Println("关闭续租") + }() + + return nil +} + +func UnRegister(name, addr, schema string) error { + if cli != nil { + //fmt.Println("unregister...") + _, err := cli.Delete(context.Background(), "/"+schema+"/"+name+"/"+addr) + return err + } + + return nil +} diff --git a/pkg/naming/resolver.go b/pkg/naming/resolver.go new file mode 100644 index 0000000..fc5f922 --- /dev/null +++ b/pkg/naming/resolver.go @@ -0,0 +1,112 @@ +package naming + +import ( + "context" + "log" + "strings" + "time" + + "go.etcd.io/etcd/api/v3/mvccpb" + "go.etcd.io/etcd/client/v3" + "google.golang.org/grpc/resolver" +) + +var schema string +var cli *clientv3.Client + +type etcdResolver struct { + rawAddr string + schema string + cc resolver.ClientConn +} + +func NewResolver(etcdAddr, schema string) resolver.Builder { + return &etcdResolver{rawAddr: etcdAddr, schema: schema} +} + +func (r *etcdResolver) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) { + //fmt.Println("target:", target) + var err error + if cli == nil { + cli, err = clientv3.New(clientv3.Config{ + Endpoints: strings.Split(r.rawAddr, ";"), + DialTimeout: 15 * time.Second, + }) + if err != nil { + return nil, err + } + } + + r.cc = cc + + go r.watch("/" + target.Scheme + "/" + target.Endpoint + "/") + + return r, nil +} + +func (r etcdResolver) Scheme() string { + return r.schema +} + +func (r etcdResolver) ResolveNow(rn resolver.ResolveNowOptions) { + //log.Println("ResolveNow") +} + +func (r etcdResolver) Close() { + //log.Println("Close") +} + +func (r *etcdResolver) watch(keyPrefix string) { + var addrList []resolver.Address + + getResp, err := cli.Get(context.Background(), keyPrefix, clientv3.WithPrefix()) + if err != nil { + log.Println(err) + } else { + for i := range getResp.Kvs { + addrList = append(addrList, resolver.Address{Addr: strings.TrimPrefix(string(getResp.Kvs[i].Key), keyPrefix)}) + } + } + + // 新版本etcd去除了NewAddress方法 以UpdateState代替 + r.cc.UpdateState(resolver.State{Addresses: addrList}) + + rch := cli.Watch(context.Background(), keyPrefix, clientv3.WithPrefix()) + for n := range rch { + for _, ev := range n.Events { + addr := strings.TrimPrefix(string(ev.Kv.Key), keyPrefix) + switch ev.Type { + case mvccpb.PUT: + if !exist(addrList, addr) { + addrList = append(addrList, resolver.Address{Addr: addr}) + r.cc.UpdateState(resolver.State{Addresses: addrList}) + } + case mvccpb.DELETE: + if s, ok := remove(addrList, addr); ok { + addrList = s + r.cc.UpdateState(resolver.State{Addresses: addrList}) + } + } + log.Printf("%s %q : %q\n", ev.Type, ev.Kv.Key, ev.Kv.Value) + } + } +} + +func exist(l []resolver.Address, addr string) bool { + for i := range l { + if l[i].Addr == addr { + return true + } + } + return false +} + +func remove(s []resolver.Address, addr string) ([]resolver.Address, bool) { + for i := range s { + if s[i].Addr == addr { + s[i] = s[len(s)-1] + return s[:len(s)-1], true + } + } + return nil, false +} diff --git a/pkg/net/grpc/client.go b/pkg/net/grpc/client.go new file mode 100644 index 0000000..9ba01ee --- /dev/null +++ b/pkg/net/grpc/client.go @@ -0,0 +1,74 @@ +package grpc + +import ( + "context" + "fmt" + "time" + + "google.golang.org/grpc" + "google.golang.org/grpc/balancer/roundrobin" + "google.golang.org/grpc/keepalive" +) + +const ( + grpcInitialWindowSize = 1 << 24 + grpcInitialConnWindowSize = 1 << 24 + grpcMaxSendMsgSize = 1 << 24 + grpcMaxCallMsgSize = 1 << 24 + grpcKeepAliveTime = time.Second * 10 + grpcKeepAliveTimeout = time.Second * 3 + grpcBackoffMaxDelay = time.Second * 3 +) + +func NewGRPCConn(addr string, timeout time.Duration) (*grpc.ClientConn, error) { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + return grpc.DialContext(ctx, addr, + []grpc.DialOption{ + grpc.WithInsecure(), + grpc.WithInitialWindowSize(grpcInitialWindowSize), + grpc.WithInitialConnWindowSize(grpcInitialConnWindowSize), + grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(grpcMaxCallMsgSize)), + grpc.WithDefaultCallOptions(grpc.MaxCallSendMsgSize(grpcMaxSendMsgSize)), + grpc.WithBackoffMaxDelay(grpcBackoffMaxDelay), + grpc.WithKeepaliveParams(keepalive.ClientParameters{ + Time: grpcKeepAliveTime, + Timeout: grpcKeepAliveTimeout, + PermitWithoutStream: true, + }), + grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"LoadBalancingPolicy": "%s"}`, roundrobin.Name)), + }...) +} + +func NewGRPCConnWithOpts(addr string, timeout time.Duration, opts ...grpc.DialOption) (*grpc.ClientConn, error) { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + dialOpts := []grpc.DialOption{ + grpc.WithInsecure(), + grpc.WithInitialWindowSize(grpcInitialWindowSize), + grpc.WithInitialConnWindowSize(grpcInitialConnWindowSize), + grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(grpcMaxCallMsgSize)), + grpc.WithDefaultCallOptions(grpc.MaxCallSendMsgSize(grpcMaxSendMsgSize)), + grpc.WithBackoffMaxDelay(grpcBackoffMaxDelay), + grpc.WithKeepaliveParams(keepalive.ClientParameters{ + Time: grpcKeepAliveTime, + Timeout: grpcKeepAliveTimeout, + PermitWithoutStream: true, + }), + grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"LoadBalancingPolicy": "%s"}`, roundrobin.Name)), + } + + dialOpts = append(dialOpts, opts...) + + return grpc.DialContext(ctx, addr, dialOpts...) +} + +//// ClientConfig is rpc client conf. +//type ClientConfig struct { +// Dial time.Duration +// Timeout time.Duration +// +// Address string +//} diff --git a/pkg/net/grpc/conf.go b/pkg/net/grpc/conf.go new file mode 100644 index 0000000..262b005 --- /dev/null +++ b/pkg/net/grpc/conf.go @@ -0,0 +1,7 @@ +package grpc + +type Discovery struct { + Address string + Name string + Password string +} diff --git a/pkg/net/grpc/server.go b/pkg/net/grpc/server.go new file mode 100644 index 0000000..0d347f3 --- /dev/null +++ b/pkg/net/grpc/server.go @@ -0,0 +1,122 @@ +package grpc + +import ( + "context" + "math" + "net" + xtime "time" + + "gitlab.33.cn/chat/dtalk/pkg/time" + "google.golang.org/grpc" + "google.golang.org/grpc/keepalive" + "google.golang.org/grpc/reflection" +) + +var ( + _abortIndex int8 = math.MaxInt8 / 2 +) + +// ServerConfig is rpc server conf. +type ServerConfig struct { + // Network is grpc listen network,default value is tcp + Network string `dsn:"network"` + // Addr is grpc listen addr,default value is 0.0.0.0:9000 + Addr string `dsn:"address"` + // Timeout is context timeout for per rpc call. + Timeout time.Duration `dsn:"query.timeout"` + // IdleTimeout is a duration for the amount of time after which an idle connection would be closed by sending a GoAway. + // Idleness duration is defined since the most recent time the number of outstanding RPCs became zero or the connection establishment. + KeepAliveMaxConnectionIdle time.Duration `dsn:"query.idleTimeout"` + // MaxLifeTime is a duration for the maximum amount of time a connection may exist before it will be closed by sending a GoAway. + // A random jitter of +/-10% will be added to MaxConnectionAge to spread out connection storms. + KeepAliveMaxConnectionAge time.Duration `dsn:"query.maxLife"` + // ForceCloseWait is an additive period after MaxLifeTime after which the connection will be forcibly closed. + KeepAliveMaxMaxConnectionAgeGrace time.Duration `dsn:"query.closeWait"` + // KeepAliveInterval is after a duration of this time if the server doesn't see any activity it pings the client to see if the transport is still alive. + KeepAliveTime time.Duration `dsn:"query.keepaliveInterval"` + // KeepAliveTimeout is After having pinged for keepalive check, the server waits for a duration of Timeout and if no activity is seen even after that + // the connection is closed. + KeepAliveTimeout time.Duration `dsn:"query.keepaliveTimeout"` +} + +func NewServer(conf *ServerConfig, opt ...grpc.ServerOption) *Server { + if conf == nil { + //TODO 远程读取 + panic("no config") + } + + s := new(Server) + s.conf = conf + + keepParam := grpc.KeepaliveParams(keepalive.ServerParameters{ + MaxConnectionIdle: xtime.Duration(s.conf.KeepAliveMaxConnectionIdle), + MaxConnectionAgeGrace: xtime.Duration(s.conf.KeepAliveMaxMaxConnectionAgeGrace), + Time: xtime.Duration(s.conf.KeepAliveTime), + Timeout: xtime.Duration(s.conf.KeepAliveTimeout), + MaxConnectionAge: xtime.Duration(s.conf.KeepAliveMaxConnectionAge), + }) + opt = append(opt, keepParam) + s.server = grpc.NewServer(opt...) + return s +} + +type Server struct { + conf *ServerConfig + server *grpc.Server + + handlers []grpc.UnaryServerInterceptor +} + +// Server return the grpc server for registering service. +func (s *Server) Server() *grpc.Server { + return s.server +} + +// Use attachs a global inteceptor to the server. +// For example, this is the right place for a rate limiter or error management inteceptor. +func (s *Server) Use(handlers ...grpc.UnaryServerInterceptor) *Server { + finalSize := len(s.handlers) + len(handlers) + if finalSize >= int(_abortIndex) { + panic("warden: server use too many handlers") + } + mergedHandlers := make([]grpc.UnaryServerInterceptor, finalSize) + copy(mergedHandlers, s.handlers) + copy(mergedHandlers[len(s.handlers):], handlers) + s.handlers = mergedHandlers + return s +} + +// Start create a new goroutine run server with configured listen addr +// will panic if any error happend +// return server itself +func (s *Server) Start() (*Server, error) { + lis, err := net.Listen(s.conf.Network, s.conf.Addr) + if err != nil { + return nil, err + } + reflection.Register(s.server) + go func() { + if err := s.server.Serve(lis); err != nil { + panic(err) + } + }() + return s, nil +} + +// Shutdown stops the server gracefully. It stops the server from +// accepting new connections and RPCs and blocks until all the pending RPCs are +// finished or the context deadline is reached. +func (s *Server) Shutdown(ctx context.Context) (err error) { + ch := make(chan struct{}) + go func() { + s.server.GracefulStop() + close(ch) + }() + select { + case <-ctx.Done(): + s.server.Stop() + err = ctx.Err() + case <-ch: + } + return +} diff --git a/pkg/net/http/http.go b/pkg/net/http/http.go new file mode 100644 index 0000000..880e1d6 --- /dev/null +++ b/pkg/net/http/http.go @@ -0,0 +1,90 @@ +package http + +import ( + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + "time" +) + +const HttpReqTimeout = 20 * time.Second + +func HttpGet(url string) ([]byte, error) { + resp, err := http.Get(url) + + if nil != resp { + defer func() { + err := resp.Body.Close() + if err != nil { + } + }() + } + + if nil != err { + return nil, err + } + + return ioutil.ReadAll(resp.Body) +} + +func HTTPPostForm(reqUrl string, headers map[string]string, payload io.Reader) ([]byte, error) { + if headers == nil { + headers = make(map[string]string) + } + + headers["Content-Type"] = "application/x-www-form-urlencoded" + return HTTPRequest("POST", reqUrl, headers, payload) +} + +// HTTPPostJSON with json []byte +func HTTPPostJSON(reqUrl string, headers map[string]string, payload io.Reader) ([]byte, error) { + if headers == nil { + headers = make(map[string]string) + } + headers["Content-Type"] = "text/json" + return HTTPRequest("POST", reqUrl, headers, payload) +} + +func HTTPRequest(method string, reqUrl string, headers map[string]string, payload io.Reader) ([]byte, error) { + req, err := http.NewRequest(method, reqUrl, payload) + if err != nil { + return nil, errors.New("make http request error: " + err.Error()) + } + + for k, v := range headers { + req.Header.Add(k, v) + } + + /*ctx := req.Context() + nextCtx, _ := context.WithDeadline(ctx, time.Now().Add(HttpReqTimeout)) + req.WithContext(nextCtx)*/ + + client := &http.Client{ + Timeout: HttpReqTimeout, + } + resp, err := client.Do(req) + if resp != nil { + defer func() { + err := resp.Body.Close() + if err != nil { + } + }() + } + + if err != nil { + return nil, errors.New("do http request error: " + err.Error()) + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, errors.New("ready http response error: " + err.Error()) + } + + if resp.StatusCode != 200 { + return body, fmt.Errorf(fmt.Sprintf("HttpStatusCode:%d, Desc:%s", resp.StatusCode, string(body))) + } + + return body, nil +} diff --git a/pkg/net/http/parse.go b/pkg/net/http/parse.go new file mode 100644 index 0000000..bfac723 --- /dev/null +++ b/pkg/net/http/parse.go @@ -0,0 +1,303 @@ +package http + +import ( + "fmt" + "reflect" + "strconv" +) + +func ParseInterface(orign interface{}, ty string) (interface{}, error) { + var result interface{} + + switch ty { + case "": + return nil, fmt.Errorf("invalid ty") + case "int": + switch tOrign := orign.(type) { + case nil: + return nil, fmt.Errorf("tOrign is nil") + case int: + result = int(tOrign) + case uint: + result = int(tOrign) + case int32: + result = int(tOrign) + case uint32: + result = int(tOrign) + case int64: + result = int(tOrign) + case uint64: + result = int(tOrign) + case float32: + result = int(tOrign) + case float64: + result = int(tOrign) + case string: + tm, err := strconv.ParseInt(tOrign, 10, 64) + if nil != err { + return nil, err + } + result = int(tm) + default: + return nil, fmt.Errorf("unknow tOrign type, " + reflect.TypeOf(tOrign).String()) + } + case "uint": + switch tOrign := orign.(type) { + case nil: + return nil, fmt.Errorf("tOrign is nil") + case int: + result = uint(tOrign) + case uint: + result = uint(tOrign) + case int32: + result = uint(tOrign) + case uint32: + result = uint(tOrign) + case int64: + result = uint(tOrign) + case uint64: + result = uint(tOrign) + case float32: + result = uint(tOrign) + case float64: + result = uint(tOrign) + case string: + tm, err := strconv.ParseUint(tOrign, 10, 64) + if nil != err { + return nil, err + } + result = uint(tm) + default: + return nil, fmt.Errorf("unknow tOrign type, " + reflect.TypeOf(tOrign).String()) + } + case "int32": + switch tOrign := orign.(type) { + case nil: + return nil, fmt.Errorf("tOrign is nil") + case int: + result = int32(tOrign) + case uint: + result = int32(tOrign) + case int32: + result = int32(tOrign) + case uint32: + result = int32(tOrign) + case int64: + result = int32(tOrign) + case uint64: + result = int32(tOrign) + case float32: + result = int32(tOrign) + case float64: + result = int32(tOrign) + case string: + tm, err := strconv.ParseInt(tOrign, 10, 64) + if nil != err { + return nil, err + } + result = int32(tm) + default: + return nil, fmt.Errorf("unknow tOrign type, " + reflect.TypeOf(tOrign).String()) + } + case "uint32": + switch tOrign := orign.(type) { + case nil: + return nil, fmt.Errorf("tOrign is nil") + case int: + result = uint32(tOrign) + case uint: + result = uint32(tOrign) + case int32: + result = uint32(tOrign) + case uint32: + result = uint32(tOrign) + case int64: + result = uint32(tOrign) + case uint64: + result = uint32(tOrign) + case float32: + result = uint32(tOrign) + case float64: + result = uint32(tOrign) + case string: + tm, err := strconv.ParseUint(tOrign, 10, 64) + if nil != err { + return nil, err + } + result = uint32(tm) + default: + return nil, fmt.Errorf("unknow tOrign type, " + reflect.TypeOf(tOrign).String()) + } + case "int64": + switch tOrign := orign.(type) { + case nil: + return nil, fmt.Errorf("tOrign is nil") + case int: + result = int64(tOrign) + case uint: + result = int64(tOrign) + case int32: + result = int64(tOrign) + case uint32: + result = int64(tOrign) + case int64: + result = int64(tOrign) + case uint64: + result = int64(tOrign) + case float32: + result = int64(tOrign) + case float64: + result = int64(tOrign) + case string: + tm, err := strconv.ParseInt(tOrign, 10, 64) + if nil != err { + return nil, err + } + result = int64(tm) + default: + return nil, fmt.Errorf("unknow tOrign type, " + reflect.TypeOf(tOrign).String()) + } + case "uint64": + switch tOrign := orign.(type) { + case nil: + return nil, fmt.Errorf("tOrign is nil") + case int: + result = uint64(tOrign) + case uint: + result = uint64(tOrign) + case int32: + result = uint64(tOrign) + case uint32: + result = uint64(tOrign) + case int64: + result = uint64(tOrign) + case uint64: + result = uint64(tOrign) + case float32: + result = uint64(tOrign) + case float64: + result = uint64(tOrign) + case string: + tm, err := strconv.ParseUint(tOrign, 10, 64) + if nil != err { + return nil, err + } + result = uint64(tm) + default: + return nil, fmt.Errorf("unknow tOrign type, " + reflect.TypeOf(tOrign).String()) + } + case "float32": + switch tOrign := orign.(type) { + case nil: + return nil, fmt.Errorf("tOrign is nil") + case int: + result = float32(tOrign) + case uint: + result = float32(tOrign) + case int32: + result = float32(tOrign) + case uint32: + result = float32(tOrign) + case int64: + result = float32(tOrign) + case uint64: + result = float32(tOrign) + case float32: + result = float32(tOrign) + case float64: + result = float32(tOrign) + case string: + tm, err := strconv.ParseFloat(tOrign, 32) + if nil != err { + return nil, err + } + result = float32(tm) + default: + return nil, fmt.Errorf("unknow tOrign type, " + reflect.TypeOf(tOrign).String()) + } + case "float64": + switch tOrign := orign.(type) { + case nil: + return nil, fmt.Errorf("tOrign is nil") + case int: + result = float64(tOrign) + case uint: + result = float64(tOrign) + case int32: + result = float64(tOrign) + case uint32: + result = float64(tOrign) + case int64: + result = float64(tOrign) + case uint64: + result = float64(tOrign) + case float32: + result = float64(tOrign) + case float64: + result = float64(tOrign) + case string: + tm, err := strconv.ParseFloat(tOrign, 64) + if nil != err { + return nil, err + } + result = float64(tm) + default: + return nil, fmt.Errorf("unknow tOrign type, " + reflect.TypeOf(tOrign).String()) + } + case "string": + switch tOrign := orign.(type) { + case nil: + return nil, fmt.Errorf("tOrign is nil") + case int: + result = fmt.Sprint(uint64(tOrign)) + case uint: + result = fmt.Sprint(uint64(tOrign)) + case int32: + result = fmt.Sprint(uint64(tOrign)) + case uint32: + result = fmt.Sprint(uint64(tOrign)) + case int64: + result = fmt.Sprint(uint64(tOrign)) + case uint64: + result = fmt.Sprint(uint64(tOrign)) + case float32: + // 这种只适合整数转字符串的情形, 也就是id那种情况, 带小数的转换不支持 + if float64(tOrign) > float64(uint64(tOrign)) { + return nil, fmt.Errorf("not support the condition, float64(tOrign) > float64(uint64(tOrign))") + } + + result = fmt.Sprint(uint64(tOrign)) + case float64: + // 这种只适合整数转字符串的情形, 也就是id那种情况, 带小数的转换不支持 + if tOrign > float64(uint64(tOrign)) { + return nil, fmt.Errorf("not support the condition, tOrign > float64(uint64(tOrign))") + } + result = fmt.Sprint(uint64(tOrign)) + case string: + result = tOrign + case bool: + result = fmt.Sprint(tOrign) + default: + return nil, fmt.Errorf("unknow tOrign type, " + reflect.TypeOf(tOrign).String()) + } + case "bool": + switch tOrign := orign.(type) { + case nil: + return nil, fmt.Errorf("tOrign is nil") + case bool: + result = tOrign + case string: + tm, err := strconv.ParseBool(tOrign) + if nil != err { + return nil, err + } + result = tm + default: + return nil, fmt.Errorf("unknow tOrign type, " + reflect.TypeOf(tOrign).String()) + } + default: + return nil, fmt.Errorf("unknow ty") + } + + return result, nil +} diff --git a/pkg/oss/aliyun/aliyun.go b/pkg/oss/aliyun/aliyun.go new file mode 100644 index 0000000..e8f7794 --- /dev/null +++ b/pkg/oss/aliyun/aliyun.go @@ -0,0 +1,323 @@ +package aliyun + +import ( + "encoding/json" + "fmt" + "io" + "sort" + "time" + + "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests" + "github.com/aliyun/alibaba-cloud-sdk-go/services/sts" + alioss "github.com/aliyun/aliyun-oss-go-sdk/oss" + "github.com/pkg/errors" + "github.com/rs/zerolog/log" + "gitlab.33.cn/chat/dtalk/pkg/oss" +) + +var _ oss.Oss = (*Aliyun)(nil) + +type Role struct { + RoleSessionName string `json:"roleSessionName"` + RoleArn string `json:"roleArn"` +} + +type Aliyun struct { + cfg *oss.Config + client *alioss.Client +} + +func New(cfg *oss.Config) *Aliyun { + ali := &Aliyun{ + cfg: cfg, + client: nil, + } + + // 创建并维护客户端 + //go ali.watchClient() + err := ali.createClient() + if err != nil { + panic(err) + } + + return ali +} + +// Config 返回配置信息 +func (ali *Aliyun) Config() *oss.Config { + return ali.cfg +} + +// AssumeRole 返回临时授权角色 +func (ali *Aliyun) AssumeRole() (*oss.AssumeRoleResp, error) { + //构建一个阿里云客户端, 用于发起请求。 + //构建阿里云客户端时,需要设置AccessKey ID和AccessKey Secret。 + /* + regionId官方示例就是写死的 + https://help.aliyun.com/document_detail/184381.htm?spm=a2c4g.11186623.2.19.25eb4bceQK29Sb#concept-1955433 + */ + client, err := sts.NewClientWithAccessKey(ali.cfg.RegionId, ali.cfg.AccessKeyId, ali.cfg.AccessKeySecret) + if err != nil { + return nil, errors.WithMessage(err, "sts.NewClientWithAccessKey()") + } + //构建请求对象。 + request := sts.CreateAssumeRoleRequest() + request.Scheme = "https" + + var role Role + err = json.Unmarshal([]byte(ali.cfg.Role), &role) + if err != nil { + return nil, errors.WithMessagef(err, "json.Unmarshal role=%+v", role) + } + //设置参数。关于参数含义和设置方法,请参见API参考。 + request.RoleArn = role.RoleArn + request.RoleSessionName = role.RoleSessionName + // 设置过期时间 + request.DurationSeconds = requests.NewInteger(ali.cfg.DurationSeconds) + + //发起请求,并得到响应。 + response, err := client.AssumeRole(request) + if err != nil { + return nil, errors.WithMessage(err, "client.AssumeRole()") + } + return &oss.AssumeRoleResp{ + RequestId: response.RequestId, + Credentials: oss.Credentials{ + AccessKeySecret: response.Credentials.AccessKeySecret, + Expiration: response.Credentials.Expiration, + AccessKeyId: response.Credentials.AccessKeyId, + SecurityToken: response.Credentials.SecurityToken, + }, + AssumedRoleUser: oss.AssumedRoleUser{ + AssumedRoleId: response.AssumedRoleUser.AssumedRoleId, + Arn: response.AssumedRoleUser.Arn, + }, + }, nil +} + +// Upload 上传文件 +// key string 文件名, 包括路径 "dtalk/2021-07-01/1.jpg" +// body io.Reader 文件内容. +// url string 文件资源链接. +// error it's nil if no error, otherwise it's an error object. +func (ali *Aliyun) Upload(key string, body io.Reader, size int64) (url, uri string, err error) { + bucket, err := ali.client.Bucket(ali.cfg.Bucket) + if err != nil { + return "", "", errors.WithMessage(err, "client.Bucket()") + } + + // 指定存储类型为标准存储,缺省也为标准存储。 + storageType := alioss.ObjectStorageClass(alioss.StorageStandard) + + // 指定存储类型为归档存储。 + // storageType := alioss.ObjectStorageClass(alioss.StorageArchive) + + // 指定访问权限为公共读,缺省为继承bucket的权限。 + objectAcl := alioss.ObjectACL(alioss.ACLPublicRead) + + // 上传字符串。 + err = bucket.PutObject(key, body, storageType, objectAcl) + if err != nil { + return "", "", errors.WithMessage(err, "bucket.PutObject()") + } + + return ali.parseUrl(key), ali.parseUri(key), nil +} + +func (ali *Aliyun) createClient() error { + client, err := alioss.New( + ali.cfg.EndPoint, + ali.cfg.AccessKeyId, + ali.cfg.AccessKeySecret, + ) + if err != nil { + return errors.WithMessage(err, "alioss.New") + } + + ali.client = client + + return nil +} + +// watchClient 定时更新临时客户端 +func (ali *Aliyun) watchClient() { + // 创建一个计时器 + durationTime := time.Second * time.Duration(ali.cfg.DurationSeconds) + timeTickerChan := time.Tick(durationTime) + + // 更新客户端 + for { + if err := ali.createTemporaryClient(); err != nil { + log.Error().Err(err).Msg("create ali temporary Client err") + } else { + log.Debug().Msg("create ali temporary Client success") + } + <-timeTickerChan + } +} + +// createTemporaryClient 创建临时客户端 +func (ali *Aliyun) createTemporaryClient() (err error) { + // 获得临时角色 + assumeRoleResp, err := ali.AssumeRole() + if err != nil { + return errors.WithMessage(err, "ali.AssumeRole()") + } + + // 创建临时客户端 + ali.client, err = alioss.New(ali.cfg.EndPoint, + assumeRoleResp.Credentials.AccessKeyId, + assumeRoleResp.Credentials.AccessKeySecret, + alioss.SecurityToken(assumeRoleResp.Credentials.SecurityToken)) + if err != nil { + return errors.WithMessage(err, "alioss.New()") + } + + return nil +} + +// parseUrl 获取对象在阿里云OSS上的完整访问URL +func (ali *Aliyun) parseUrl(key string) string { + return fmt.Sprintf("https://%s.%s/%s", ali.cfg.Bucket, ali.cfg.EndPoint, key) +} + +// parseUri ... +func (ali *Aliyun) parseUri(key string) string { + return fmt.Sprintf("/%s", key) +} + +// GetHost ... +func (ali *Aliyun) GetHost() string { + return fmt.Sprintf("https://%s.%s", ali.cfg.Bucket, ali.cfg.EndPoint) +} + +// InitiateMultipartUpload 初始化分段上传任务 +// 使用分段上传方式传输数据前,必须先通知OBS初始化一个分段上传任务。 +// 该操作会返回一个OBS服务端创建的全局唯一标识(Upload ID),用于标识本次分段上传任务。 +// 您可以根据这个唯一标识来发起相关的操作,如取消分段上传任务、列举分段上传任务、列举已上传的段等。 +// +// key string +// +// uploadId string +// err error +func (ali *Aliyun) InitiateMultipartUpload(key string) (uploadId string, err error) { + bucket, err := ali.client.Bucket(ali.cfg.Bucket) + if err != nil { + return "", errors.WithMessage(err, "client.Bucket()") + } + + imur, err := bucket.InitiateMultipartUpload(key) + if err != nil { + return "", errors.WithMessage(err, "bucket.InitiateMultipartUpload") + } + + return imur.UploadID, nil +} + +// UploadPart 上传段 +// 初始化一个分段上传任务之后,可以根据指定的对象名和Upload ID来分段上传数据。 +// 每一个上传的段都有一个标识它的号码——分段号(Part Number,范围是1~10000)。 +// 对于同一个Upload ID,该分段号不但唯一标识这一段数据,也标识了这段数据在整个对象内的相对位置。 +// 如果您用同一个分段号上传了新的数据,那么OBS上已有的这个段号的数据将被覆盖。 +// 除了最后一段以外,其他段的大小范围是100KB~5GB;最后段大小范围是0~5GB。 +// 每个段不需要按顺序上传,甚至可以在不同进程、不同机器上上传,OBS会按照分段号排序组成最终对象。 +// +// key string +// uploadId string +// body io.Reader +// partNumber int32 +// offset int64 +// partSize int64 +// +// ETag string +// err error +func (ali *Aliyun) UploadPart(key, uploadId string, body io.Reader, partNumber int32, offset, + partSize int64) (ETag string, err error) { + // TODO offset, partSize 好像是不需要的参数, 如果 body 是 io.Reader 的话 + bucket, err := ali.client.Bucket(ali.cfg.Bucket) + if err != nil { + return "", errors.WithMessage(err, "client.Bucket") + } + + imur := alioss.InitiateMultipartUploadResult{ + Key: key, + UploadID: uploadId, + Bucket: ali.cfg.Bucket, + } + + part, err := bucket.UploadPart(imur, body, partSize, int(partNumber)) + if err != nil { + return "", errors.WithMessage(err, "bucket.UploadPart") + } + return part.ETag, nil +} + +// CompleteMultipartUpload 合并段 +// 所有分段上传完成后,需要调用合并段接口来在OBS服务端生成最终对象。 +// 在执行该操作时,需要提供所有有效的分段列表(包括分段号和分段ETag值); +// OBS收到提交的分段列表后,会逐一验证每个段的有效性。当所有段验证通过后,OBS将把这些分段组合成最终的对象。 +// +// key string +// uploadId string +// parts []model.Part +// +// url string +// err error +func (ali *Aliyun) CompleteMultipartUpload(key, uploadId string, parts []oss.Part) (url, uri string, err error) { + bucket, err := ali.client.Bucket(ali.cfg.Bucket) + if err != nil { + return "", "", errors.WithMessage(err, "client.Bucket") + } + + imur := alioss.InitiateMultipartUploadResult{ + Key: key, + UploadID: uploadId, + Bucket: ali.cfg.Bucket, + } + + ossParts := make([]alioss.UploadPart, len(parts), len(parts)) + sort.Sort(oss.Parts(parts)) + for i := range parts { + ossParts[i] = alioss.UploadPart{ + PartNumber: int(parts[i].PartNumber), + ETag: parts[i].ETag, + } + } + + _, err = bucket.CompleteMultipartUpload(imur, ossParts) + if err != nil { + return "", "", errors.WithMessage(err, "bucket.CompleteMultipartUpload") + } + return ali.parseUrl(key), ali.parseUri(key), nil +} + +// AbortMultipartUpload 取消分段上传任务 +// 分段上传任务可以被取消,当一个分段上传任务被取消后,就不能再使用其Upload ID做任何操作,已经上传段也会被OBS删除。 +// 采用分段上传方式上传对象过程中或上传对象失败后会在桶内产生段,这些段会占用您的存储空间,您可以通过取消该分段上传任务来清理掉不需要的段,节约存储空间。 +// +// key string +// uploadId string +// +// err error +func (ali *Aliyun) AbortMultipartUpload(key, uploadId string) error { + bucket, err := ali.client.Bucket(ali.cfg.Bucket) + if err != nil { + return errors.WithMessage(err, "client.Bucket") + } + + imur := alioss.InitiateMultipartUploadResult{ + Key: key, + UploadID: uploadId, + Bucket: ali.cfg.Bucket, + } + + err = bucket.AbortMultipartUpload(imur) + if err != nil { + return errors.WithMessage(err, "bucket.AbortMultipartUpload") + } + return nil +} + +func (ali *Aliyun) test() { + //ali.client. +} diff --git a/pkg/oss/aliyun/aliyun_test.go b/pkg/oss/aliyun/aliyun_test.go new file mode 100644 index 0000000..ea4e8d7 --- /dev/null +++ b/pkg/oss/aliyun/aliyun_test.go @@ -0,0 +1,70 @@ +package aliyun + +import ( + "encoding/json" + "testing" + + "gitlab.33.cn/chat/dtalk/pkg/oss" +) + +var ( + AccessKeyId = "LTAI5tHhExpCkBhMgc9A1Apz" + AccessKeySecret = "vb6VaS7ir1aPuN9dFh4e3HkJyzL8iD" + role = `{ +"roleSessionName": "otc-test", +"roleArn": "acs:ram::1483023416825300:role/otc-test" +}` + policy = `` + RegionId = "cn-hangzhou" + DurationSeconds = 3600 + Bucket = "chy-otc-chat" + EndPoint = "oss-cn-shanghai.aliyuncs.com" +) + +func TestAliyun_AssumeRole(t *testing.T) { + type fields struct { + cfg *oss.Config + } + tests := []struct { + name string + fields fields + want *oss.AssumeRoleResp + wantErr bool + }{ + { + name: "", + fields: fields{ + cfg: &oss.Config{ + RegionId: RegionId, + Bucket: Bucket, + EndPoint: EndPoint, + AccessKeyId: AccessKeyId, + AccessKeySecret: AccessKeySecret, + Role: role, + Policy: policy, + DurationSeconds: DurationSeconds, + }, + }, + want: nil, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ali := &Aliyun{ + cfg: tt.fields.cfg, + } + got, err := ali.AssumeRole() + if (err != nil) != tt.wantErr { + t.Errorf("AssumeRole() error = %v, wantErr %v", err, tt.wantErr) + return + } + data, err := json.Marshal(got) + if err != nil { + t.Errorf("Marshal result error = %v", err) + return + } + t.Log("got:", string(data)) + }) + } +} diff --git a/pkg/oss/huaweiyun/huaweiyun.go b/pkg/oss/huaweiyun/huaweiyun.go new file mode 100644 index 0000000..32c3c20 --- /dev/null +++ b/pkg/oss/huaweiyun/huaweiyun.go @@ -0,0 +1,358 @@ +package huaweiyun + +import ( + "encoding/json" + "fmt" + "io" + "sort" + "time" + + "github.com/huaweicloud/huaweicloud-sdk-go-obs/obs" + "github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/global" + iam "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/iam/v3" + iamModel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/iam/v3/model" + region "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/iam/v3/region" + "github.com/pkg/errors" + "github.com/rs/zerolog/log" + "gitlab.33.cn/chat/dtalk/pkg/oss" +) + +var _ oss.Oss = (*Huaweiyun)(nil) + +type Policy struct { + Version string `json:"Version"` + Statement []Statement `json:"Statement"` +} + +type Statement struct { + Action []string `json:"Action"` + Effect string `json:"Effect"` +} + +type Huaweiyun struct { + cfg *oss.Config + client *obs.ObsClient +} + +// New 创建一个 Huaweiyun 对象 +func New(cfg *oss.Config) *Huaweiyun { + huawei := &Huaweiyun{ + cfg: cfg, + client: nil, + } + + err := huawei.createClient() + if err != nil { + panic(err) + } + + return huawei +} + +// Config 返回 Huaweiyun Config 信息 +func (huawei *Huaweiyun) Config() *oss.Config { + return huawei.cfg +} + +// AssumeRole 创建临时授权角色 +func (huawei *Huaweiyun) AssumeRole() (*oss.AssumeRoleResp, error) { + ak := huawei.cfg.AccessKeyId + sk := huawei.cfg.AccessKeySecret + + //构建一个华为云客户端, 用于发起请求。 + //构建华为云客户端时,需要设置AccessKey ID, AccessKey Secret和区域代码。 + auth := global.NewCredentialsBuilder(). + WithAk(ak). + WithSk(sk). + Build() + + client := iam.NewIamClient( + iam.IamClientBuilder(). + WithRegion(region.ValueOf(huawei.cfg.RegionId)). + WithCredential(auth). + Build()) + + // 构建请求对象 + request := &iamModel.CreateTemporaryAccessKeyByTokenRequest{} + + // 设置参数, 具体参考https://support.huaweicloud.com/api-iam/iam_04_0002.html + // 设置 policy 策略 + var policy Policy + err := json.Unmarshal([]byte(huawei.cfg.Policy), &policy) //第二个参数要地址传递 + if err != nil { + return nil, err + } + + var listStatementPolicy []iamModel.ServiceStatement + for _, statement := range policy.Statement { + var listActionStatement []string + for _, action := range statement.Action { + listActionStatement = append(listActionStatement, action) + } + + var serviceStatement iamModel.ServiceStatement + serviceStatement.Action = listActionStatement + if statement.Effect == "Allow" { + serviceStatement.Effect = iamModel.GetServiceStatementEffectEnum().ALLOW + } else { + serviceStatement.Effect = iamModel.GetServiceStatementEffectEnum().DENY + } + listStatementPolicy = append(listStatementPolicy, serviceStatement) + } + + policyIdentity := &iamModel.ServicePolicy{ + Version: policy.Version, + Statement: listStatementPolicy, + } + + // 设置有效时间 + durationSecondsTokenIdentityToken := int32(huawei.cfg.DurationSeconds) + tokenIdentity := &iamModel.IdentityToken{ + DurationSeconds: &durationSecondsTokenIdentityToken, + } + + // 设置Token + var listMethodsIdentity = []iamModel.TokenAuthIdentityMethods{ + iamModel.GetTokenAuthIdentityMethodsEnum().TOKEN, + } + + // 认证 + identityAuth := &iamModel.TokenAuthIdentity{ + Methods: listMethodsIdentity, + Token: tokenIdentity, + Policy: policyIdentity, + } + authbody := &iamModel.TokenAuth{ + Identity: identityAuth, + } + + request.Body = &iamModel.CreateTemporaryAccessKeyByTokenRequestBody{ + Auth: authbody, + } + + //发起请求,并得到响应。 + response, err := client.CreateTemporaryAccessKeyByToken(request) + if err != nil { + return nil, err + } + + return &oss.AssumeRoleResp{ + RequestId: "", /*response.RequestId*/ + Credentials: oss.Credentials{ + AccessKeySecret: response.Credential.Secret, + Expiration: response.Credential.ExpiresAt, + AccessKeyId: response.Credential.Access, + SecurityToken: response.Credential.Securitytoken, + }, + AssumedRoleUser: oss.AssumedRoleUser{ + AssumedRoleId: "", /*response.AssumedRoleUser.AssumedRoleId*/ + Arn: "", /*response.AssumedRoleUser.Arn*/ + }, + }, err + +} + +// Upload 上传文件 +func (huawei *Huaweiyun) Upload(key string, body io.Reader, size int64) (url, uri string, err error) { + input := &obs.PutObjectInput{} + input.Bucket = huawei.cfg.Bucket + input.Key = key + input.Body = body + input.ContentLength = size + _, err = huawei.client.PutObject(input) + if err != nil { + return "", "", errors.WithMessage(err, "huawei.client.PutObject()") + } + return huawei.parseUrl(key), huawei.parseUri(key), nil +} + +// watchClient 定时更新临时客户端 +func (huawei *Huaweiyun) watchClient() { + // 创建一个计时器 + durationTime := time.Second * time.Duration(huawei.cfg.DurationSeconds) + timeTickerChan := time.Tick(durationTime) + + // 更新临时客户端 + for { + if err := huawei.createTemporaryClient(); err != nil { + log.Error().Err(err).Msg("create huawei Client err") + } else { + log.Debug().Msg("create huawei Client success") + } + <-timeTickerChan + } +} + +// createTemporaryClient 创建临时客户端 +func (huawei *Huaweiyun) createTemporaryClient() error { + assumeRoleResp, err := huawei.AssumeRole() + if err != nil { + return errors.WithMessage(err, "huawei.AssumeRole()") + } + // 创建临时客户端 + huawei.client, err = obs.New( + assumeRoleResp.Credentials.AccessKeyId, + assumeRoleResp.Credentials.AccessKeySecret, + huawei.cfg.EndPoint, + obs.WithSecurityToken(assumeRoleResp.Credentials.SecurityToken)) + if err != nil { + return errors.WithMessage(err, "huawei.obs.New()") + } + return nil +} + +// createClient 创建永久客户端 +func (huawei *Huaweiyun) createClient() (err error) { + // 创建临时客户端 + huawei.client, err = obs.New( + huawei.cfg.AccessKeyId, + huawei.cfg.AccessKeySecret, + huawei.cfg.EndPoint, + obs.WithMaxRetryCount(5), + ) + if err != nil { + return errors.WithMessage(err, "huawei.obs.New()") + } + return nil +} + +// parseUrl 获取对象在 Huaweiyun 上的完整访问URL +func (huawei *Huaweiyun) parseUrl(key string) string { + return fmt.Sprintf("https://%s.%s/%s", huawei.cfg.Bucket, huawei.cfg.EndPoint, key) +} + +// InitiateMultipartUpload 初始化分段上传任务 +// 使用分段上传方式传输数据前,必须先通知OBS初始化一个分段上传任务。 +// 该操作会返回一个OBS服务端创建的全局唯一标识(Upload ID),用于标识本次分段上传任务。 +// 您可以根据这个唯一标识来发起相关的操作,如取消分段上传任务、列举分段上传任务、列举已上传的段等。 +// +// key string +// +// uploadId string +// err error +func (huawei *Huaweiyun) InitiateMultipartUpload(key string) (uploadId string, err error) { + input := &obs.InitiateMultipartUploadInput{} + input.Bucket = huawei.cfg.Bucket + input.Key = key + + output, err := huawei.client.InitiateMultipartUpload(input) + if err != nil { + return "", errors.WithMessage(err, "huawei.client.InitiateMultipartUpload") + } + return output.UploadId, nil +} + +// UploadPart 上传段 +// 初始化一个分段上传任务之后,可以根据指定的对象名和Upload ID来分段上传数据。 +// 每一个上传的段都有一个标识它的号码——分段号(Part Number,范围是1~10000)。 +// 对于同一个Upload ID,该分段号不但唯一标识这一段数据,也标识了这段数据在整个对象内的相对位置。 +// 如果您用同一个分段号上传了新的数据,那么OBS上已有的这个段号的数据将被覆盖。 +// 除了最后一段以外,其他段的大小范围是100KB~5GB;最后段大小范围是0~5GB。 +// 每个段不需要按顺序上传,甚至可以在不同进程、不同机器上上传,OBS会按照分段号排序组成最终对象。 +// +// key string +// uploadId string +// body io.Reader +// partNumber int32 +// offset int64 +// partSize int64 +// +// ETag string +// err error +func (huawei *Huaweiyun) UploadPart(key, uploadId string, body io.Reader, partNumber int32, offset, + partSize int64) (ETag string, err error) { + // TODO offset, partSize 好像是不需要的参数, 如果 body 是 io.Reader 的话 + input := &obs.UploadPartInput{} + input.Bucket = huawei.cfg.Bucket + input.Key = key + input.UploadId = uploadId + input.Body = body + input.PartNumber = int(partNumber) + input.Offset = offset + input.PartSize = partSize + + output, err := huawei.client.UploadPart(input) + if err != nil { + return "", errors.WithMessage(err, "huawei.client.UploadPart") + } + return output.ETag, nil +} + +// CompleteMultipartUpload 合并段 +// 所有分段上传完成后,需要调用合并段接口来在OBS服务端生成最终对象。 +// 在执行该操作时,需要提供所有有效的分段列表(包括分段号和分段ETag值); +// OBS收到提交的分段列表后,会逐一验证每个段的有效性。当所有段验证通过后,OBS将把这些分段组合成最终的对象。 +// +// key string +// uploadId string +// parts []oss.Part +// +// url string +// err error +func (huawei *Huaweiyun) CompleteMultipartUpload(key, uploadId string, parts []oss.Part) (url, uri string, err error) { + input := &obs.CompleteMultipartUploadInput{} + input.Bucket = huawei.cfg.Bucket + input.Key = key + input.UploadId = uploadId + obsParts := make([]obs.Part, len(parts), len(parts)) + sort.Sort(oss.Parts(parts)) + for i := range parts { + obsParts[i] = obs.Part{ + PartNumber: int(parts[i].PartNumber), + ETag: parts[i].ETag, + } + } + input.Parts = obsParts + + _, err = huawei.client.CompleteMultipartUpload(input) + if err != nil { + return "", "", errors.WithMessage(err, "huawei.client.CompleteMultipartUpload") + } + return huawei.parseUrl(key), huawei.parseUri(key), nil +} + +// AbortMultipartUpload 取消分段上传任务 +// 分段上传任务可以被取消,当一个分段上传任务被取消后,就不能再使用其Upload ID做任何操作,已经上传段也会被OBS删除。 +// 采用分段上传方式上传对象过程中或上传对象失败后会在桶内产生段,这些段会占用您的存储空间,您可以通过取消该分段上传任务来清理掉不需要的段,节约存储空间。 +// +// key string +// uploadId string +// +// err error +func (huawei *Huaweiyun) AbortMultipartUpload(key, uploadId string) error { + input := &obs.AbortMultipartUploadInput{} + input.Bucket = huawei.cfg.Bucket + input.Key = key + input.UploadId = uploadId + + _, err := huawei.client.AbortMultipartUpload(input) + if err != nil { + return errors.WithMessage(err, "huawei.client.AbortMultipartUpload") + } + return nil +} + +func (huawei *Huaweiyun) test() { + input := &obs.InitiateMultipartUploadInput{} + input.Bucket = huawei.cfg.Bucket + input.Key = "objectname" + input.ContentType = "text/plain" + //input.Metadata = map[string]string{"property1": "property-value1", "property2": "property-value2"} + output, err := huawei.client.InitiateMultipartUpload(input) + if err == nil { + fmt.Printf("UploadId:%s\n", output.UploadId) + } else if obsError, ok := err.(obs.ObsError); ok { + fmt.Printf("Code:%s\n", obsError.Code) + fmt.Printf("Message:%s\n", obsError.Message) + } +} + +// parseUri ... +func (huawei *Huaweiyun) parseUri(key string) string { + return fmt.Sprintf("/%s", key) +} + +// GetHost ... +func (huawei *Huaweiyun) GetHost() string { + return fmt.Sprintf("https://%s.%s", huawei.cfg.Bucket, huawei.cfg.EndPoint) +} diff --git a/pkg/oss/huaweiyun/huaweiyun_test.go b/pkg/oss/huaweiyun/huaweiyun_test.go new file mode 100644 index 0000000..3a450e1 --- /dev/null +++ b/pkg/oss/huaweiyun/huaweiyun_test.go @@ -0,0 +1,76 @@ +package huaweiyun + +import ( + "encoding/json" + "testing" + + "gitlab.33.cn/chat/dtalk/pkg/oss" +) + +var ( + AccessKeyId = "VS1SNCU6SM7NSRLFT0HF" + AccessKeySecret = "pRI6OjPTrc0atS3Do4PFgSOyx7IOnaXXbDf5ZBd2" + //endPointRegion := "cn-east-3" + policy = ` +{ + "Version": "1.1", + "Statement": [ + { + "Action": [ + "obs:object:*" + ], + "Effect": "Allow" + } + ] +}` + RegionId = "cn-east-3" + DurationSeconds = 3600 + Bucket = "chy-cdn" + EndPoint = "obs.cn-east-3.myhuaweicloud.com" +) + +func TestHuaweiyun_AssumeRole(t *testing.T) { + type fields struct { + cfg *oss.Config + } + tests := []struct { + name string + fields fields + want *oss.AssumeRoleResp + wantErr bool + }{ + { + name: "huaweiyun_getTempSk_test", + fields: fields{ + cfg: &oss.Config{ + RegionId: RegionId, + Bucket: Bucket, + EndPoint: EndPoint, + AccessKeyId: AccessKeyId, + AccessKeySecret: AccessKeySecret, + Role: "", + Policy: policy, + DurationSeconds: DurationSeconds, + }, + }, + want: nil, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + huawei := New(tt.fields.cfg) + got, err := huawei.AssumeRole() + if (err != nil) != tt.wantErr { + t.Errorf("AssumeRole() error = %v, wantErr %v", err, tt.wantErr) + return + } + data, err := json.Marshal(got) + if err != nil { + t.Errorf("Marshal result error = %v", err) + return + } + t.Log("got:", string(data)) + }) + } +} diff --git a/pkg/oss/minio/minio.go b/pkg/oss/minio/minio.go new file mode 100644 index 0000000..cca83c1 --- /dev/null +++ b/pkg/oss/minio/minio.go @@ -0,0 +1,125 @@ +package minio + +import ( + "context" + "fmt" + "github.com/minio/minio-go/v7" + "github.com/minio/minio-go/v7/pkg/credentials" + "github.com/pkg/errors" + "gitlab.33.cn/chat/dtalk/pkg/oss" + "io" + "sort" +) + +var _ oss.Oss = (*Minio)(nil) + +type Minio struct { + cfg *oss.Config + client *minio.Core +} + +func New(cfg *oss.Config) *Minio { + m := &Minio{ + cfg: cfg, + client: nil, + } + err := m.createClient() + if err != nil { + panic(err) + } + + return m + +} + +func (m *Minio) Config() *oss.Config { + return m.cfg +} + +func (m *Minio) AssumeRole() (*oss.AssumeRoleResp, error) { + return nil, errors.New("minio not support assume role") +} + +func (m *Minio) Upload(key string, body io.Reader, size int64) (url, uri string, err error) { + ctx := context.Background() + _, err = m.client.PutObject(ctx, m.cfg.Bucket, key, body, size, "", "", minio.PutObjectOptions{}) + if err != nil { + return "", "", errors.WithMessage(err, "minio.client.PutObject") + } + return m.parseUrl(key), m.parseUri(key), nil +} + +func (m *Minio) InitiateMultipartUpload(key string) (uploadId string, err error) { + ctx := context.Background() + uploadId, err = m.client.NewMultipartUpload(ctx, m.cfg.Bucket, key, minio.PutObjectOptions{}) + if err != nil { + return "", errors.WithMessage(err, "minio.client.NewMultipartUpload") + } + return uploadId, nil +} + +func (m *Minio) UploadPart(key, uploadId string, body io.Reader, partNumber int32, offset, + partSize int64) (ETag string, err error) { + ctx := context.Background() + output, err := m.client.PutObjectPart(ctx, m.cfg.Bucket, key, uploadId, int(partNumber), body, partSize, "", "", nil) + if err != nil { + return "", errors.WithMessage(err, "minio.client.PutObjectPart") + } + return output.ETag, nil +} + +func (m *Minio) CompleteMultipartUpload(key, uploadId string, parts []oss.Part) (url, uri string, err error) { + ctx := context.Background() + minioParts := make([]minio.CompletePart, len(parts), len(parts)) + sort.Sort(oss.Parts(parts)) + for i := range parts { + minioParts[i] = minio.CompletePart{ + ETag: parts[i].ETag, + PartNumber: int(parts[i].PartNumber), + } + } + + _, err = m.client.CompleteMultipartUpload(ctx, m.cfg.Bucket, key, uploadId, minioParts, minio.PutObjectOptions{}) + if err != nil { + return "", "", errors.WithMessage(err, "minio.client.CompleteMultipartUpload") + } + return m.parseUrl(key), m.parseUri(key), nil +} + +func (m *Minio) AbortMultipartUpload(key, uploadId string) error { + ctx := context.Background() + err := m.client.AbortMultipartUpload(ctx, m.cfg.Bucket, key, uploadId) + if err != nil { + return errors.WithMessage(err, "minio.client.AbortMultipartUpload") + } + return nil +} + +func (m *Minio) createClient() (err error) { + m.client, err = minio.NewCore( + m.cfg.EndPoint, + &minio.Options{ + Creds: credentials.NewStaticV4(m.cfg.AccessKeyId, m.cfg.AccessKeySecret, ""), + // todo + Secure: false, + }) + if err != nil { + return errors.WithMessage(err, "minio minio.NewCore") + } + return nil +} + +// parseUrl 获取对象在 minio 上的完整访问URL +func (m *Minio) parseUrl(key string) string { + return fmt.Sprintf("%s/%s/%s", m.cfg.PublicUrl, m.cfg.Bucket, key) +} + +// parseUri ... +func (m *Minio) parseUri(key string) string { + return fmt.Sprintf("/%s/%s", m.cfg.Bucket, key) +} + +// GetHost ... +func (m *Minio) GetHost() string { + return fmt.Sprintf("http://%s", m.cfg.EndPoint) +} diff --git a/pkg/oss/minio/minio_test.go b/pkg/oss/minio/minio_test.go new file mode 100644 index 0000000..27f4420 --- /dev/null +++ b/pkg/oss/minio/minio_test.go @@ -0,0 +1,141 @@ +package minio + +import ( + "bytes" + "github.com/stretchr/testify/require" + "gitlab.33.cn/chat/dtalk/pkg/oss" + "math" + "os" + "sort" + "testing" +) + +/* +[[Oss]] +AppId = "dtalk" +OssType = "minio" +RegionId = "" +AccessKeyId = "XYI4T3QIT8YQQLRHA0YV" +AccessKeySecret = "FqgvB3CzsBK5xwEphEC6i4Y6dTkWAjyfQ9TS1kLZ" +Role = "" +Policy = "" +DurationSeconds = 3600 +Bucket = "dtalk-test" +EndPoint = "127.0.0.1:9000" +*/ + +var ( + minioConfig = &oss.Config{ + RegionId: "", + AccessKeyId: "XYI4T3QIT8YQQLRHA0YV", + AccessKeySecret: "FqgvB3CzsBK5xwEphEC6i4Y6dTkWAjyfQ9TS1kLZ", + Role: "", + Policy: "", + DurationSeconds: 3600, + Bucket: "dtalk-test", + EndPoint: "127.0.0.1:9000", + } + smFilePath = "/Users/cccccccccchy/Downloads/oa.sql" + bgFilePath = "/Users/cccccccccchy/Downloads/movie_bg.mp4" + OssMinio = &Minio{} +) + +func TestMain(m *testing.M) { + OssMinio = New(minioConfig) + os.Exit(m.Run()) +} + +func TestMinio_Upload_sm(t *testing.T) { + file, err := os.OpenFile(smFilePath, os.O_RDONLY, os.ModePerm) + if err != nil { + panic(err) + } + fileStat, err := file.Stat() + if err != nil { + panic(err) + } + url, _, err := OssMinio.Upload("test_smFile.gif", file, fileStat.Size()) + require.NoError(t, err) + t.Log(url) +} + +func TestMinio_Upload_bg(t *testing.T) { + file, err := os.OpenFile(bgFilePath, os.O_RDONLY, os.ModePerm) + if err != nil { + panic(err) + } + fileStat, err := file.Stat() + if err != nil { + panic(err) + } + url, _, err := OssMinio.Upload("test_movie_bg.mp4", file, fileStat.Size()) + require.NoError(t, err) + t.Log(url) +} + +func TestMinio_Multipart_bg(t *testing.T) { + file, err := os.OpenFile(smFilePath, os.O_RDONLY, os.ModePerm) + if err != nil { + panic(err) + } + fileStat, err := file.Stat() + if err != nil { + panic(err) + } + + var chunkSize int64 = 1024 * 1024 * 5 + var num int64 + var key string = "oa.sql" + var uploadId string + var parts []oss.Part + + uploadId, err = OssMinio.InitiateMultipartUpload(key) + require.NoError(t, err) + + num = int64(math.Ceil(float64(fileStat.Size()) / float64(chunkSize))) + + // 执行并发上传段 + partChan := make(chan oss.Part, num) + var i int64 = 0 + for ; i < num; i++ { + go func(i int64) { + b := make([]byte, chunkSize) + _, _ = file.Seek(i*(chunkSize), 0) + if len(b) > int(fileStat.Size()-i*chunkSize) { + b = make([]byte, fileStat.Size()-i*chunkSize) + } + + file.Read(b) + r := bytes.NewReader(b) + etag, err := OssMinio.UploadPart(key, uploadId, r, int32(i+1), 0, int64(len(b))) + require.NoError(t, err) + partChan <- oss.Part{ + ETag: etag, + PartNumber: int32(i + 1), + } + + t.Log("partNumber", i+1, "len", len(b)) + + }(i) + } + + parts = make([]oss.Part, 0, num) + // 等待上传完成 + for { + part, ok := <-partChan + if !ok { + break + } + parts = append(parts, part) + + if len(parts) == int(num) { + close(partChan) + } + } + + sort.Sort(oss.Parts(parts)) + + url, _, err := OssMinio.CompleteMultipartUpload(key, uploadId, parts) + require.NoError(t, err) + t.Log(url) +} diff --git a/pkg/oss/oss.go b/pkg/oss/oss.go new file mode 100644 index 0000000..325d87f --- /dev/null +++ b/pkg/oss/oss.go @@ -0,0 +1,83 @@ +package oss + +import ( + "io" +) + +type Oss interface { + Config() *Config + AssumeRole() (*AssumeRoleResp, error) + Upload(string, io.Reader, int64) (url, uri string, err error) + InitiateMultipartUpload(key string) (uploadId string, err error) + UploadPart(key, uploadId string, body io.Reader, partNumber int32, offset, partSize int64) (ETag string, err error) + CompleteMultipartUpload(key, uploadId string, parts []Part) (url, uri string, err error) + AbortMultipartUpload(key, uploadId string) error + GetHost() string +} + +type Config struct { + RegionId string + AccessKeyId string + AccessKeySecret string + Role string + Policy string + DurationSeconds int + Bucket string + EndPoint string + PublicUrl string +} + +type Part struct { + // 段数据的MD5值 + ETag string `json:"ETag" form:"ETag" binding:"required"` + // 分段序号, 范围是1~10000 + PartNumber int32 `json:"partNumber" form:"partNumber" binding:"required"` +} + +// 结构体数组 +type Parts []Part + +// 下面的三个函数必须实现(获取长度函数,交换函数,比较函数) +func (p Parts) Len() int { + return len(p) +} +func (p Parts) Swap(i, j int) { + p[i], p[j] = p[j], p[i] +} +func (p Parts) Less(i, j int) bool { + return p[i].PartNumber < p[j].PartNumber +} + +//{ +//"Credentials": { +//"AccessKeyId": "STS.L4aBSCSJVMuKg5U1****", +//"AccessKeySecret": "wyLTSmsyPGP1ohvvw8xYgB29dlGI8KMiH2pK****", +//"Expiration": "2015-04-09T11:52:19Z", +//"SecurityToken": "********" +//}, +//"AssumedRoleUser": { +//"Arn": "acs:ram::123456789012****:role/adminrole/alice", +//"AssumedRoleId":"34458433936495****:alice" +//}, +//"RequestId": "6894B13B-6D71-4EF5-88FA-F32781734A7F" +//} + +type AssumeRoleResp struct { + RequestId string `json:"RequestId" xml:"RequestId"` + Credentials Credentials `json:"Credentials" xml:"Credentials"` + AssumedRoleUser AssumedRoleUser `json:"AssumedRoleUser" xml:"AssumedRoleUser"` +} + +// Credentials is a nested struct in sts response +type Credentials struct { + AccessKeySecret string `json:"AccessKeySecret" xml:"AccessKeySecret"` + Expiration string `json:"Expiration" xml:"Expiration"` + AccessKeyId string `json:"AccessKeyId" xml:"AccessKeyId"` + SecurityToken string `json:"SecurityToken" xml:"SecurityToken"` +} + +// AssumedRoleUser is a nested struct in sts response +type AssumedRoleUser struct { + AssumedRoleId string `json:"AssumedRoleId" xml:"AssumedRoleId"` + Arn string `json:"Arn" xml:"Arn"` +} diff --git a/pkg/rand/rand.go b/pkg/rand/rand.go new file mode 100644 index 0000000..7b5a635 --- /dev/null +++ b/pkg/rand/rand.go @@ -0,0 +1,80 @@ +package rand + +import ( + "crypto/rand" +) + +var ( + // stdSource standard source string. + stdSource = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + // stdNumberSource standard number source string. + stdNumberSource = "0123456789" + + // 16 + std16Source = "0123456789abcdef" +) + +// StdSource returns standard source string. +func StdSource() string { + return stdSource +} + +// StdNumberSource returns standard number source string. +func StdNumberSource() string { + return stdNumberSource +} + +func Std16Source() string { + return std16Source +} + +// NewString returns a new random string of the provided length, consisting +// of the standard source string. +// It panics if source length is wrong (<1 or >256) or rand.Read occurs an error. +func NewString(length int) string { + return NewWithSource(length, StdSource()) +} + +// NewNumber returns a new random string of the provided length, consisting +// of the standard number source string. +// It panics if source length is wrong (<1 or >256) or rand.Read occurs an error. +func NewNumber(length int) string { + return NewWithSource(length, StdNumberSource()) +} + +// NewWithSource returns a new random string of the provided length, consisting +// of the provided source string. +// It panics if source length is wrong (<1 or >256) or rand.Read occurs an error. +func NewWithSource(length int, source string) string { + if length == 0 { + return "" + } + sl := len(source) + if sl < 1 || sl > 256 { + panic("rand: wrong source length") + } + rbMax := 255 - (256 % sl) + b := make([]byte, length) + r := make([]byte, length+length/2) // storage for random bytes + i := 0 + for { + if _, err := rand.Read(r); err != nil { + panic(err) + } + for _, rb := range r { + v := int(rb) + if v > rbMax { // skip to avoid modulo bias + continue + } + b[i] = source[v%sl] + i++ + if i == length { + return string(b) + } + } + } +} + +func NewAESKey256() string { + return NewWithSource(32, Std16Source()) +} diff --git a/pkg/redis/redis.go b/pkg/redis/redis.go new file mode 100644 index 0000000..35cb62d --- /dev/null +++ b/pkg/redis/redis.go @@ -0,0 +1,161 @@ +// From https://gitlab.33.cn/proof/backend-micro/blob/dev/pkg/gredis/gredis.go + +package redis + +import ( + "encoding/json" + "github.com/gomodule/redigo/redis" + xtime "gitlab.33.cn/chat/dtalk/pkg/time" + "sync" + "time" +) + +// Config . +type Config struct { + Network string + Addr string + Auth string + Active int + Idle int + DialTimeout xtime.Duration + ReadTimeout xtime.Duration + WriteTimeout xtime.Duration + IdleTimeout xtime.Duration + Expire xtime.Duration +} + +// Pool Redis缓存池结构 +type Pool struct { + pool *redis.Pool + sync.RWMutex +} + +func New(c *Config) *Pool { + pool := &redis.Pool{ + MaxIdle: c.Idle, + MaxActive: c.Active, + IdleTimeout: time.Duration(c.IdleTimeout), + Dial: func() (redis.Conn, error) { + conn, err := redis.Dial(c.Network, c.Addr, + redis.DialConnectTimeout(time.Duration(c.DialTimeout)), + redis.DialReadTimeout(time.Duration(c.ReadTimeout)), + redis.DialWriteTimeout(time.Duration(c.WriteTimeout)), + redis.DialPassword(c.Auth), + ) + if err != nil { + return nil, err + } + return conn, nil + }, + } + return &Pool{pool: pool} +} + +// Do 向Redis服务发送命令并返回收到的答复 +func (p *Pool) Do(cmd string, args ...interface{}) (interface{}, error) { + conn := p.pool.Get() + if err := conn.Err(); err != nil { + return nil, err + } + defer conn.Close() + return conn.Do(cmd, args...) +} + +// Set 将数据data关联到给定key,time为key的超时时间(秒) +func (p *Pool) Set(key string, data interface{}, time ...int) error { + var err error + if len(time) != 0 { + _, err = p.Do("SET", key, data, "EX", time[0]) + } else { + _, err = p.Do("SET", key, data) + } + return err +} + +// Exists 检查给定key是否存在 +func (p *Pool) Exists(key string) (bool, error) { + return redis.Bool(p.Do("EXISTS", key)) +} + +// GetBytes 返回给定key所关联的[]byte值 +func (p *Pool) GetBytes(key string) ([]byte, error) { + return redis.Bytes(p.Do("GET", key)) +} + +// GetString 返回给定key所关联的string值 +func (p *Pool) GetString(key string) (string, error) { + return redis.String(p.Do("GET", key)) +} + +// GetInt 返回给定key所关联的int值 +func (p *Pool) GetInt(key string) (int, error) { + return redis.Int(p.Do("GET", key)) +} + +// GetInt64 返回给定key所关联的int64值 +func (p *Pool) GetInt64(key string) (int64, error) { + return redis.Int64(p.Do("GET", key)) +} + +// Read 将给定key所关联的值反序列化到obj对象 +func (p *Pool) Read(key string, obj interface{}) error { + if data, err := p.GetBytes(key); err == nil { + return json.Unmarshal(data, obj) + } else { + return err + } +} + +// Write 将数据data序列化后关联到给定key,time为key的超时时间(秒) +func (p *Pool) Write(key string, obj interface{}, time ...int) error { + data, err := json.Marshal(obj) + if err != nil { + return err + } + if len(time) != 0 { + _, err = p.Do("SET", key, data, "EX", time[0]) + } else { + _, err = p.Do("SET", key, data) + } + return err +} + +// Del 删除给定key +func (p *Pool) Del(key string) (bool, error) { + return redis.Bool(p.Do("DEL", key)) +} + +// LikeDel 模糊删除给定key +//func (p *Pool) LikeDel(key string) error { +// keys, err := redis.Strings(p.Do("KEYS", "*"+key+"*")) +// if err != nil { +// return err +// } +// _, err = redis.Bool(p.Do("DEL", sliceUtil.StringsToInterfaces(keys)...)) +// return err +//} + +// TTL 返回给定key的剩余生存时间(秒) +func (p *Pool) TTL(key string) (int, error) { + return redis.Int(p.Do("TTL", key)) +} + +// Incr 将key所储存的数字值增一 +func (p *Pool) Incr(key string) (int64, error) { + return redis.Int64(p.Do("INCR", key)) +} + +// IncrBy 将key所储存的数字值加上增量increment +func (p *Pool) IncrBy(key string, increment int64) (int64, error) { + return redis.Int64(p.Do("INCRBY", key, increment)) +} + +// Decr 将key所储存的数字值减一 +func (p *Pool) Decr(key string) (int64, error) { + return redis.Int64(p.Do("DECR", key)) +} + +// DecrBy 将key所储存的数字值减去减量decrement +func (p *Pool) DecrBy(key string, decrement int64) (int64, error) { + return redis.Int64(p.Do("DECRBY", key, decrement)) +} diff --git a/pkg/sign/tencentyun/tlssig.go b/pkg/sign/tencentyun/tlssig.go new file mode 100644 index 0000000..8dbd594 --- /dev/null +++ b/pkg/sign/tencentyun/tlssig.go @@ -0,0 +1,71 @@ +package tencentyun + +import ( + "github.com/tencentyun/tls-sig-api-v2-golang/tencentyun" +) + +type TLSSig interface { + GetAppId() int32 + GetUserSig(userId string) (string, error) + GenPrivateMapKey(userId string, roomId int32, privilegeMap int32) (string, error) +} + +// TCTLSSig 腾讯音视频签名实例 +type TCTLSSig struct { + sdkAppId int + secretKey string + expire int +} + +func NewTCTLSSig(sdkAppId int, secretKey string, expire int) TLSSig { + return &TCTLSSig{ + sdkAppId: sdkAppId, + secretKey: secretKey, + expire: expire, + } +} + +func (t *TCTLSSig) GetAppId() int32 { + return int32(t.sdkAppId) +} + +//GetUserSig +//【功能说明】用于签发 TRTC 和 IM 服务中必须要使用的 UserSig 鉴权票据 +// +//参数说明】 +//sdkappid - 应用id +//key - 计算 usersig 用的加密密钥,控制台可获取 +//userid - 用户id,限制长度为32字节,只允许包含大小写英文字母(a-zA-Z)、数字(0-9)及下划线和连词符。 +//expire - UserSig 票据的过期时间,单位是秒,比如 86400 代表生成的 UserSig 票据在一天后就无法再使用了。 +func (t *TCTLSSig) GetUserSig(userId string) (string, error) { + return tencentyun.GenUserSig(t.sdkAppId, t.secretKey, userId, t.expire) +} + +// GenPrivateMapKey +//【功能说明】 +// 用于签发 TRTC 进房参数中可选的 PrivateMapKey 权限票据。 +// PrivateMapKey 需要跟 UserSig 一起使用,但 PrivateMapKey 比 UserSig 有更强的权限控制能力: +// - UserSig 只能控制某个 UserID 有无使用 TRTC 服务的权限,只要 UserSig 正确,其对应的 UserID 可以进出任意房间。 +// - PrivateMapKey 则是将 UserID 的权限控制的更加严格,包括能不能进入某个房间,能不能在该房间里上行音视频等等。 +// 如果要开启 PrivateMapKey 严格权限位校验,需要在【实时音视频控制台】=>【应用管理】=>【应用信息】中打开“启动权限密钥”开关。 +// +//【参数说明】 +// sdkappid - 应用id。 +// key - 计算 usersig 用的加密密钥,控制台可获取。 +// userid - 用户id,限制长度为32字节,只允许包含大小写英文字母(a-zA-Z)、数字(0-9)及下划线和连词符。 +// expire - PrivateMapKey 票据的过期时间,单位是秒,比如 86400 生成的 PrivateMapKey 票据在一天后就无法再使用了。 +// roomid - 房间号,用于指定该 userid 可以进入的房间号 +// privilegeMap - 权限位,使用了一个字节中的 8 个比特位,分别代表八个具体的功能权限开关: +// - 第 1 位:0000 0001 = 1,创建房间的权限 +// - 第 2 位:0000 0010 = 2,加入房间的权限 +// - 第 3 位:0000 0100 = 4,发送语音的权限 +// - 第 4 位:0000 1000 = 8,接收语音的权限 +// - 第 5 位:0001 0000 = 16,发送视频的权限 +// - 第 6 位:0010 0000 = 32,接收视频的权限 +// - 第 7 位:0100 0000 = 64,发送辅路(也就是屏幕分享)视频的权限 +// - 第 8 位:1000 0000 = 200,接收辅路(也就是屏幕分享)视频的权限 +// - privilegeMap == 1111 1111 == 255 代表该 userid 在该 roomid 房间内的所有功能权限。 +// - privilegeMap == 0010 1010 == 42 代表该 userid 拥有加入房间和接收音视频数据的权限,但不具备其他权限。 +func (t *TCTLSSig) GenPrivateMapKey(userId string, roomId int32, privilegeMap int32) (string, error) { + return tencentyun.GenPrivateMapKey(t.sdkAppId, t.secretKey, userId, t.expire, uint32(roomId), uint32(privilegeMap)) +} diff --git a/pkg/sign/tencentyun/tlssig_test.go b/pkg/sign/tencentyun/tlssig_test.go new file mode 100644 index 0000000..23f287a --- /dev/null +++ b/pkg/sign/tencentyun/tlssig_test.go @@ -0,0 +1,29 @@ +package tencentyun + +import ( + "testing" +) + +func TestGenUserSign(t *testing.T) { + ts := NewTCTLSSig(1400543084, "1ed1b5e2729395c1e8b55b83be72e60139dfa36ff3fa67bc3c3285592a7b3cf6", 86400) + userSign1, err := ts.GetUserSig("AAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB") + if err != nil { + + } + t.Log(userSign1) + userSign2, err := ts.GetUserSig("AAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB") + if err != nil { + + } + t.Log(userSign2) + userSign3, err := ts.GetUserSig("share_chenhongyu") + if err != nil { + + } + t.Log(userSign3) + userSign4, err := ts.GetUserSig("chenhongyu") + if err != nil { + + } + t.Log(userSign4) +} diff --git a/pkg/time/debug.go b/pkg/time/debug.go new file mode 100644 index 0000000..55ca80f --- /dev/null +++ b/pkg/time/debug.go @@ -0,0 +1,6 @@ +package time + +const ( + // Debug debug switch + Debug = false +) diff --git a/pkg/time/duration.go b/pkg/time/duration.go new file mode 100644 index 0000000..4f193c5 --- /dev/null +++ b/pkg/time/duration.go @@ -0,0 +1,17 @@ +package time + +import ( + xtime "time" +) + +// Duration be used toml unmarshal string time, like 1s, 500ms. +type Duration xtime.Duration + +// UnmarshalText unmarshal text to duration. +func (d *Duration) UnmarshalText(text []byte) error { + tmp, err := xtime.ParseDuration(string(text)) + if err == nil { + *d = Duration(tmp) + } + return err +} diff --git a/pkg/time/duration_test.go b/pkg/time/duration_test.go new file mode 100644 index 0000000..61b16cd --- /dev/null +++ b/pkg/time/duration_test.go @@ -0,0 +1,20 @@ +package time + +import ( + "testing" + "time" +) + +func TestDurationText(t *testing.T) { + var ( + input = []byte("10s") + output = time.Second * 10 + d Duration + ) + if err := d.UnmarshalText(input); err != nil { + t.FailNow() + } + if int64(output) != int64(d) { + t.FailNow() + } +} diff --git a/pkg/time/timer.go b/pkg/time/timer.go new file mode 100644 index 0000000..454d0e0 --- /dev/null +++ b/pkg/time/timer.go @@ -0,0 +1,263 @@ +package time + +import ( + "log" + "sync" + itime "time" +) + +const ( + timerFormat = "2006-01-02 15:04:05" + infiniteDuration = itime.Duration(1<<63 - 1) +) + +// TimerData timer data. +type TimerData struct { + Key string + expire itime.Time + fn func() + index int + next *TimerData +} + +// Delay delay duration. +func (td *TimerData) Delay() itime.Duration { + return itime.Until(td.expire) +} + +// ExpireString expire string. +func (td *TimerData) ExpireString() string { + return td.expire.Format(timerFormat) +} + +// Timer timer. +type Timer struct { + lock sync.Mutex + free *TimerData + timers []*TimerData + signal *itime.Timer + num int +} + +// NewTimer new a timer. +// A heap must be initialized before any of the heap operations +// can be used. Init is idempotent with respect to the heap invariants +// and may be called whenever the heap invariants may have been invalidated. +// Its complexity is O(n) where n = h.Len(). +// +func NewTimer(num int) (t *Timer) { + t = new(Timer) + t.init(num) + return t +} + +// Init init the timer. +func (t *Timer) Init(num int) { + t.init(num) +} + +func (t *Timer) init(num int) { + t.signal = itime.NewTimer(infiniteDuration) + t.timers = make([]*TimerData, 0, num) + t.num = num + t.grow() + go t.start() +} + +func (t *Timer) grow() { + var ( + i int + td *TimerData + tds = make([]TimerData, t.num) + ) + t.free = &(tds[0]) + td = t.free + for i = 1; i < t.num; i++ { + td.next = &(tds[i]) + td = td.next + } + td.next = nil +} + +// get get a free timer data. +func (t *Timer) get() (td *TimerData) { + if td = t.free; td == nil { + t.grow() + td = t.free + } + t.free = td.next + return +} + +// put put back a timer data. +func (t *Timer) put(td *TimerData) { + td.fn = nil + td.next = t.free + t.free = td +} + +// Add add the element x onto the heap. The complexity is +// O(log(n)) where n = h.Len(). +func (t *Timer) Add(expire itime.Duration, fn func()) (td *TimerData) { + t.lock.Lock() + td = t.get() + td.expire = itime.Now().Add(expire) + td.fn = fn + t.add(td) + t.lock.Unlock() + return +} + +// Del removes the element at index i from the heap. +// The complexity is O(log(n)) where n = h.Len(). +func (t *Timer) Del(td *TimerData) { + t.lock.Lock() + t.del(td) + t.put(td) + t.lock.Unlock() +} + +// Push pushes the element x onto the heap. The complexity is +// O(log(n)) where n = h.Len(). +func (t *Timer) add(td *TimerData) { + var d itime.Duration + td.index = len(t.timers) + // add to the minheap last node + t.timers = append(t.timers, td) + t.up(td.index) + if td.index == 0 { + // if first node, signal start goroutine + d = td.Delay() + t.signal.Reset(d) + if Debug { + log.Printf("timer: add reset delay %d ms", int64(d)/int64(itime.Millisecond)) + } + } + if Debug { + log.Printf("timer: push item key: %s, expire: %s, index: %d", td.Key, td.ExpireString(), td.index) + } +} + +func (t *Timer) del(td *TimerData) { + var ( + i = td.index + last = len(t.timers) - 1 + ) + if i < 0 || i > last || t.timers[i] != td { + // already remove, usually by expire + if Debug { + log.Printf("timer del i: %d, last: %d, %p", i, last, td) + } + return + } + if i != last { + t.swap(i, last) + t.down(i, last) + t.up(i) + } + // remove item is the last node + t.timers[last].index = -1 // for safety + t.timers = t.timers[:last] + if Debug { + log.Printf("timer: remove item key: %s, expire: %s, index: %d", td.Key, td.ExpireString(), td.index) + } +} + +// Set update timer data. +func (t *Timer) Set(td *TimerData, expire itime.Duration) { + t.lock.Lock() + t.del(td) + td.expire = itime.Now().Add(expire) + t.add(td) + t.lock.Unlock() +} + +// start start the timer. +func (t *Timer) start() { + for { + t.expire() + <-t.signal.C + } +} + +// expire removes the minimum element (according to Less) from the heap. +// The complexity is O(log(n)) where n = max. +// It is equivalent to Del(0). +func (t *Timer) expire() { + var ( + fn func() + td *TimerData + d itime.Duration + ) + t.lock.Lock() + for { + if len(t.timers) == 0 { + d = infiniteDuration + if Debug { + log.Print("timer: no other instance") + } + break + } + td = t.timers[0] + if d = td.Delay(); d > 0 { + break + } + fn = td.fn + // let caller put back + t.del(td) + t.lock.Unlock() + if fn == nil { + log.Print("expire timer no fn") + } else { + if Debug { + log.Printf("timer key: %s, expire: %s, index: %d expired, call fn", td.Key, td.ExpireString(), td.index) + } + fn() + } + t.lock.Lock() + } + t.signal.Reset(d) + if Debug { + log.Printf("timer: expier reset delay %d ms", int64(d)/int64(itime.Millisecond)) + } + t.lock.Unlock() +} + +func (t *Timer) up(j int) { + for { + i := (j - 1) / 2 // parent + if i >= j || !t.less(j, i) { + break + } + t.swap(i, j) + j = i + } +} + +func (t *Timer) down(i, n int) { + for { + j1 := 2*i + 1 + if j1 >= n || j1 < 0 { // j1 < 0 after int overflow + break + } + j := j1 // left child + if j2 := j1 + 1; j2 < n && !t.less(j1, j2) { + j = j2 // = 2*i + 2 // right child + } + if !t.less(j, i) { + break + } + t.swap(i, j) + i = j + } +} + +func (t *Timer) less(i, j int) bool { + return t.timers[i].expire.Before(t.timers[j].expire) +} + +func (t *Timer) swap(i, j int) { + t.timers[i], t.timers[j] = t.timers[j], t.timers[i] + t.timers[i].index = i + t.timers[j].index = j +} diff --git a/pkg/time/timer_test.go b/pkg/time/timer_test.go new file mode 100644 index 0000000..6010358 --- /dev/null +++ b/pkg/time/timer_test.go @@ -0,0 +1,43 @@ +package time + +import ( + "testing" + "time" + + log "github.com/golang/glog" +) + +func TestTimer(t *testing.T) { + timer := NewTimer(100) + tds := make([]*TimerData, 100) + for i := 0; i < 100; i++ { + tds[i] = timer.Add(time.Duration(i)*time.Second+5*time.Minute, nil) + } + printTimer(timer) + for i := 0; i < 100; i++ { + log.Infof("td: %s, %s, %d", tds[i].Key, tds[i].ExpireString(), tds[i].index) + timer.Del(tds[i]) + } + printTimer(timer) + for i := 0; i < 100; i++ { + tds[i] = timer.Add(time.Duration(i)*time.Second+5*time.Minute, nil) + } + printTimer(timer) + for i := 0; i < 100; i++ { + timer.Del(tds[i]) + } + printTimer(timer) + timer.Add(time.Second, nil) + time.Sleep(time.Second * 2) + if len(timer.timers) != 0 { + t.FailNow() + } +} + +func printTimer(timer *Timer) { + log.Infof("----------timers: %d ----------", len(timer.timers)) + for i := 0; i < len(timer.timers); i++ { + log.Infof("timer: %s, %s, index: %d", timer.timers[i].Key, timer.timers[i].ExpireString(), timer.timers[i].index) + } + log.Infof("--------------------") +} diff --git a/pkg/tx/query.go b/pkg/tx/query.go new file mode 100644 index 0000000..331ff1d --- /dev/null +++ b/pkg/tx/query.go @@ -0,0 +1,33 @@ +package tx + +import ( + "context" + "time" + + chainCommon "github.com/33cn/chain33/common" + chainTypes "github.com/33cn/chain33/types" +) + +func (c *ChainClient) QueryHash(hash string) (*chainTypes.TransactionDetail, error) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) + defer cancel() + data, err := chainCommon.FromHex(hash) + if err != nil { + return nil, err + } + reqHash := &chainTypes.ReqHash{ + Hash: data, + } + return c.client.QueryTransaction(ctx, reqHash) +} + +func (c *ChainClient) GetRealTx(hash string) (*chainTypes.TransactionDetail, error) { + td, err := c.QueryHash(hash) + if err != nil { + return td, err + } + if td.Tx == nil || td.Tx.Next == nil { + return td, nil + } + return c.QueryHash(chainCommon.ToHex(td.Tx.Next)) +} diff --git a/pkg/tx/query_test.go b/pkg/tx/query_test.go new file mode 100644 index 0000000..c854af5 --- /dev/null +++ b/pkg/tx/query_test.go @@ -0,0 +1,141 @@ +package tx + +import ( + "encoding/json" + chainCommon "github.com/33cn/chain33/common" + "testing" + + chainTypes "github.com/33cn/chain33/types" +) + +var ( + BlockChainAddr = "172.16.101.87:8902" + + title = "user.p.testproofv2." + coinExec = "coins" // 平行链币消耗执行器 +) + +var config = Config{ + Grpc: Grpc{ + BlockChainAddr: BlockChainAddr, + }, + Chain: Chain{ + FeePrikey: "", + FeeAddr: "", + Title: title, + BaseExec: title + chainTypes.NoneX, + CoinExec: title + coinExec, + }, + Encrypt: Encrypt{ + Seed: "", + SignType: 1, + }, +} + +func initClient() *ChainClient { + cfg := config + return MustNewChainClient(&cfg) +} + +func TestChainClient_QueryHash(t *testing.T) { + type fields struct { + cli *ChainClient + } + type args struct { + hash string + } + tests := []struct { + name string + fields fields + args args + want *chainTypes.TransactionDetail + wantErr bool + }{ + { + name: "", + fields: fields{ + cli: initClient(), + }, + args: args{ + hash: "0xe5bee7afdf8794d76479f2d1e0bb5675ce6b9d54e86e0fcefcfa147436ce4abd", + }, + want: nil, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := tt.fields.cli + got, err := c.QueryHash(tt.args.hash) + if (err != nil) != tt.wantErr { + t.Errorf("QueryHash() error = %v, wantErr %v", err, tt.wantErr) + return + } + b, err := json.Marshal(got) + if err != nil { + t.Errorf("Json Marshal() error = %v", err) + return + } + t.Logf("got json: %v\n", string(b)) + + t.Logf("next hash: %v\n", chainCommon.ToHex(got.Tx.Next)) + + //tx2 := chainTypes.Transaction{} + //data, err := base64.StdEncoding.DecodeString(string(got.Tx.Next)) + //if err != nil { + // t.Errorf("base64.StdEncoding.DecodeString() error = %v", err) + // return + //} + //err = json.Unmarshal(data, &tx2) + //if err != nil { + // t.Errorf("json.Unmarshal() error = %v", err) + // return + //} + //t.Logf("got json: %v\n", tx2) + }) + } +} + +func TestChainClient_GetRealTx(t *testing.T) { + type fields struct { + cli *ChainClient + } + type args struct { + hash string + } + tests := []struct { + name string + fields fields + args args + want *chainTypes.TransactionDetail + wantErr bool + }{ + { + name: "", + fields: fields{ + cli: initClient(), + }, + args: args{ + hash: "0xfef6aab8dd9e65310982eda196a77c1c80494745b9be21fbf509326e010421eb", + }, + want: nil, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := tt.fields.cli + got, err := c.GetRealTx(tt.args.hash) + if (err != nil) != tt.wantErr { + t.Errorf("GetRealTx() error = %v, wantErr %v", err, tt.wantErr) + return + } + b, err := json.Marshal(got) + if err != nil { + t.Errorf("Json Marshal() error = %v", err) + return + } + t.Logf("got json: %v\n", string(b)) + }) + } +} diff --git a/pkg/tx/tx.go b/pkg/tx/tx.go new file mode 100644 index 0000000..2acc23e --- /dev/null +++ b/pkg/tx/tx.go @@ -0,0 +1,190 @@ +package tx + +import ( + "context" + "math/rand" + "time" + + chainCommon "github.com/33cn/chain33/common" + "github.com/33cn/chain33/common/address" + "github.com/33cn/chain33/rpc/grpcclient" + coinsTypes "github.com/33cn/chain33/system/dapp/coins/types" + chainTypes "github.com/33cn/chain33/types" + chainUtil "github.com/33cn/chain33/util" + "github.com/gogo/protobuf/proto" + "github.com/pkg/errors" + "google.golang.org/grpc" +) + +type ChainClient struct { + client chainTypes.Chain33Client // chain33提供的客户端 + cfg *Config + //FeePri crypto.PrivKey // 代扣私钥 +} + +// NewChainClient 新建上链客户端 +func NewChainClient(c *Config) (*ChainClient, error) { + if c == nil { + return nil, errors.New("ChainClient: illegal ChainClient configure") + } + initConfig(c) + conn, err := grpc.Dial(grpcclient.NewMultipleURL(c.Grpc.BlockChainAddr), grpc.WithInsecure()) + if err != nil { + return nil, err + } + client := chainTypes.NewChain33Client(conn) + _, err = client.GetWalletStatus(context.Background(), &chainTypes.ReqNil{}) + if err != nil { + return nil, errors.Errorf("GetWalletStatus fail Err:%v", err.Error()) + } + c.Chain.BaseExec = c.Chain.Title + chainTypes.NoneX + c.Chain.CoinExec = c.Chain.Title + c.Chain.CoinExec + if err != nil { + return nil, errors.Errorf("NewChainClient AesCbc Err:%v", err.Error()) + } + //return &ChainClient{client: client, cfg: c, FeePri: chainUtil.HexToPrivkey(c.Chain.FeePrikey)}, nil + return &ChainClient{client: client, cfg: c}, nil +} + +// MustNewChainClient 新建上链客户端 +func MustNewChainClient(c *Config) *ChainClient { + f, err := NewChainClient(c) + if err != nil { + panic(err) + } + + return f +} + +func initConfig(c *Config) { + const defaultExec = "testproofv2" + if c.Chain.ReceiveCoinAmount == 0 { + c.Chain.ReceiveCoinAmount = 100000000 + } + //if c.ProofCh == nil { + // c.ProofCh = &ProofCh{100, 3, 20, 3} + //} + //if c.Organization == nil { + // c.Organization = &Organization{10, 3, 10} + //} +} + +func (c *ChainClient) newTxInfo(exec, prikey string, payload []byte, feerate int64) (*TxInfo, error) { + //if prikey != c.cfg.Chain.FeePrikey { + // pridata, err := hex.DecodeString(prikey) + // if err != nil { + // return nil, err + // } + // pridata, err = c.cipher.Decrypt(pridata) + // if err != nil { + // return nil, err + // } + // prikey = string(pridata) + //} + + return &TxInfo{ + Exec: exec, + Payload: payload, + FeeRate: feerate, + Prikey: prikey, + }, nil +} + +// CreateTx 创建交易 +func (c *ChainClient) createTx(t *TxInfo) *chainTypes.Transaction { + tx := &chainTypes.Transaction{ + Execer: []byte(t.Exec), + Payload: t.Payload, + Nonce: rand.Int63(), + To: address.ExecAddress(t.Exec), + } + if t.FeeRate != noFeeRate { + if t.FeeRate == 0 { + t.FeeRate = minFeeRate + } + tx.SetRealFee(t.FeeRate) + } + + return tx +} + +// CreateTx 创建签名交易 +func (c *ChainClient) createSignTx(t *TxInfo) *chainTypes.Transaction { + tx := c.createTx(t) + priv := chainUtil.HexToPrivkey(t.Prikey) + tx.Sign(c.cfg.Encrypt.SignType, priv) + return tx +} + +// CreateBaseTx 创建代扣基础交易 +func (c *ChainClient) createBaseTx(exec string) *chainTypes.Transaction { + tx := &chainTypes.Transaction{ + Execer: []byte(exec), + Nonce: rand.Int63(), + To: address.ExecAddress(exec), + } + return tx +} + +// CreateBaseTx 创建项目方扣费交易 +func (c *ChainClient) createCoinTx() *chainTypes.Transaction { + transfer := &chainTypes.AssetsTransfer{ + Cointoken: "", + Amount: c.cfg.Chain.ReceiveCoinAmount, + Note: nil, + To: c.cfg.Chain.ReceiveCoinAddr, + } + cbvalue := &coinsTypes.CoinsAction_Transfer{ + Transfer: transfer, + } + cb := &coinsTypes.CoinsAction{ + Value: cbvalue, + Ty: coinsTypes.CoinsActionTransfer, + } + payloadData, _ := proto.Marshal(cb) + tx := &chainTypes.Transaction{ + Execer: []byte(c.cfg.Chain.CoinExec), + Nonce: rand.Int63(), + To: address.ExecAddress(c.cfg.Chain.CoinExec), + Payload: payloadData, + } + return tx +} + +//// CreateTxGroup 创建交易组 代扣方式 +//func (c *ChainClient) createTxGroup(t *TxInfo) (*chainTypes.Transaction, error) { +// var txs []*chainTypes.Transaction +// tx1 := c.createBaseTx(c.cfg.Chain.BaseExec) +// tx2 := c.createTx(t) +// txs = append(txs, tx1, tx2) +// if c.cfg.Chain.Coin != "" { +// tx3 := c.createCoinTx() +// txs = append(txs, tx3) +// } +// txGroup, err := chainTypes.CreateTxGroup(txs, minFeeRate) +// if err != nil { +// return nil, err +// } +// txGroup.SignN(0, c.cfg.Encrypt.SignType, c.FeePri) +// priv := chainUtil.HexToPrivkey(t.Prikey) +// txGroup.SignN(1, c.cfg.Encrypt.SignType, priv) +// if c.cfg.Chain.Coin != "" { +// txGroup.SignN(2, c.cfg.Encrypt.SignType, c.FeePri) +// } +// return txGroup.Tx(), nil +//} + +func (c *ChainClient) sendTxInfo(tx *chainTypes.Transaction) (string, error) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + defer cancel() + reply, err := c.client.SendTransaction(ctx, tx) + if err != nil { + // h, _ := c.client.GetLastHeader(context.Background(), nil) + return "", err + } + if !reply.IsOk { + return "", err + } + + return chainCommon.ToHex(reply.Msg), nil +} diff --git a/pkg/tx/types.go b/pkg/tx/types.go new file mode 100644 index 0000000..3020dfe --- /dev/null +++ b/pkg/tx/types.go @@ -0,0 +1,130 @@ +package tx + +const ( + minFeeRate = 100000 + noFeeRate = -1 +) + +// Config 上链服务相关配置 +type Config struct { + Chain Chain + Grpc Grpc + Encrypt Encrypt +} + +type Chain struct { + FeePrikey string // 代扣私钥字符串 + FeeAddr string // 代扣地址 + Title string // 虚拟平行链 + BaseExec string `json:",default=none"` // 代扣执行器 + CoinExec string `json:",default=coins"` // 平行链币消耗执行器 + TokenExec string `json:",default=token"` // 平行链币消耗执行器 + Coin string // 需要额外消耗的coin + + ReceiveCoinAddr string // 收费地址 + ReceiveCoinAmount int64 `json:",default=100000000"` // 收费数量 + BwalletReceiveCoinAddr string // 接收币钱包支付的地址 +} + +type Grpc struct { + BlockChainAddr string +} + +type Encrypt struct { + Seed string + SignType int32 `json:",default=1"` // 交易签名方式 Invalid=0,SECP256K1=1,ED25519=2,SM2=3 +} + +type TxInfo struct { + Exec string + Payload []byte + FeeRate int64 + Prikey string +} + +// 区块链上链的option字段 +type BlockProofOption struct { + Key string `json:"key"` + Value string `json:"value"` +} + +type BlockProof struct { + Data string `json:"data"` + Version string `json:"version"` + Option string `json:"option"` + Note string `json:"note"` + Ext string `json:"ext"` +} + +type BlockUserConfig struct { + Op string `json:"op"` + Organization string `json:"organization"` + Role string `json:"role"` + Address string `json:"address"` + MemberNote string `json:"member_note"` + OrganizationNote string `json:"organization_note"` +} + +// BlockDeleteProof 上链数据格式 +type BlockDeleteProof struct { + Id string `json:"id"` // 存证哈希 + Note string `json:"note"` + Force bool `json:"force"` +} + +// 模板上链格式 +type BlockTemplate struct { + Name string `json:"name"` + Data string `json:"data"` +} + +type QueryProof struct { + Id int64 + Hash string + IsBase bool +} + +type ReqChainProofs struct { + Hashs []string `json:"hash"` +} + +type RespChainProofs struct { + BaseHash string `json:"basehash"` + PreHash string `json:"prehash"` + ProofBlockHash string `json:"proof_block_hash"` + ProofBlockTime int64 `json:"proof_block_time"` + ProofData string `json:"proof_data"` + ProofDeleted string `json:"proof_deleted"` + ProofDeletedNote string `json:"proof_deleted_note"` + ProofHeight int64 `json:"proof_height"` + ProofHeightIndex int64 `json:"proof_height_index"` + ProofId string `json:"proof_id"` + ProofNote string `json:"proof_note"` + ProofOrganization string `json:"proof_organization"` + ProofSender string `json:"proof_sender"` + ProofTxHash string `json:"proof_tx_hash"` +} + +type ReqChainProofMember struct { + Address []string +} + +type RespChainProofMember struct { + Address string `json:"address"` + Role string `json:"role"` + Organization string `json:"organization"` + Note string `json:"note"` + Height int64 `json:"height"` + Ts int64 `json:"ts"` + BlockHash string `json:"block_hash"` + Index int64 `json:"index"` + Send string `json:"send"` + TxHash string `json:"tx_hash"` + HeightIndex int64 `json:"height_index"` +} + +type GenAddressAndPrikeyRes struct { + Address string `json:"address"` + Pubkey string `json:"pubkey"` + Prikey string `json:"prikey"` +} diff --git a/pkg/util/decode-cfg.go b/pkg/util/decode-cfg.go new file mode 100644 index 0000000..1e2748b --- /dev/null +++ b/pkg/util/decode-cfg.go @@ -0,0 +1,55 @@ +package util + +import ( + "os" + "path/filepath" + "runtime" + + "github.com/BurntSushi/toml" + log "github.com/inconshreveable/log15" +) + +/* + ---workdir/ + | -- bin/ + | |-- chat(I am here) + | + | -- etc/ + |-- config.toml + |-- config.json +*/ +func findFile() (string, error) { + var configPath = "" + log.Info("runtime:", "os", runtime.GOOS) + if runtime.GOOS == `windows` { + configPath = "etc/config.toml" + } else { + pwd, err := filepath.Abs(filepath.Dir(filepath.Dir(os.Args[0]))) + if err != nil { + log.Info("get project pwd err", "err", err) + return "", err + } + err = os.Chdir(pwd) + if err != nil { + log.Info("get project is empty", "err", err) + return configPath, err + } + d, _ := os.Getwd() + log.Info("project info:", "dir", d) + configPath = d + "/etc/config.toml" + } + return configPath, nil +} + +func Decode(c interface{}) error { + configPath, err := findFile() + if err != nil { + return err + } + log.Info("path:", configPath) + if _, err := toml.DecodeFile(configPath, c); err != nil { + return err + } + log.Info("config info", "cfg", c) + return nil +} diff --git a/pkg/util/secp256.go b/pkg/util/secp256.go new file mode 100644 index 0000000..9f7af62 --- /dev/null +++ b/pkg/util/secp256.go @@ -0,0 +1,142 @@ +package util + +import ( + "encoding/hex" + "fmt" + "github.com/inconshreveable/log15" + "net/url" + "reflect" + "sort" + "strconv" + "strings" + "time" + + "github.com/haltingstate/secp256k1-go" +) + +const ( + Invalid = 0 + SECP256K1 = 1 + ED25519 = 2 + SM2 = 3 +) + +//tm 时间,单位毫秒 +func CheckTimeOut(tm int64, timeout time.Duration) bool { + sec := tm / 1000 + nsec := tm % 1000 + t := time.Unix(sec, nsec).Add(timeout) + return time.Now().After(t) +} + +//func CheckTimeOut(tm int64, timeout time.Duration) bool { +// sec := tm / 1000 +// nsec := tm % 1000 +// t := time.Unix(sec, nsec) +// tExp := t.Add(timeout) +// return time.Now().After(tExp) || time.Now().Before(t) +//} + +//得到摘要 +func GetSummary(i interface{}) ([]byte, error) { + return summaryNoEncode(i) +} + +//得到摘要 +func summaryUrlEncode(i interface{}) ([]byte, error) { + params := paramsMap(i) + delete(params, "sign") + delete(params, "-") + + u := url.Values{} + for k, v := range params { + u.Set(k, v) + } + //按字典升序排序并URL编码 + secretStr := u.Encode() + + return []byte(secretStr), nil +} + +func summaryNoEncode(i interface{}) ([]byte, error) { + params := paramsMap(i) + delete(params, "sign") + delete(params, "-") + + u := url.Values{} + for k, v := range params { + u.Set(k, v) + } + + var buf strings.Builder + keys := make([]string, 0, len(u)) + for k := range u { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + vs := u[k] + for _, v := range vs { + if buf.Len() > 0 { + buf.WriteByte('&') + } + buf.WriteString(k) + buf.WriteByte('=') + buf.WriteString(v) + } + } + + //按字典升序排序 + secretStr := buf.String() + + return []byte(secretStr), nil +} + +func Secp256k1Verify(msg, sig, pubKey []byte) (b bool) { + defer func() { + if r := recover(); r != nil { + err, _ := r.(error) + log15.Error("secp256k1 VerifySignature failed", "err", err) + b = false + } + }() + return 1 == secp256k1.VerifySignature(msg, sig, pubKey) +} + +func paramsMap(i interface{}) map[string]string { + ret := make(map[string]string) + + st := reflect.TypeOf(i) + v := reflect.ValueOf(i) + + ss := st.Elem() + vv := v.Elem() + for i := 0; i < ss.NumField(); i++ { + field := ss.Field(i) + val := vv.Field(i).Interface() + keyName := ToString(field.Tag.Get("json")) + keyName = strings.Replace(keyName, ",omitempty", "", 1) + ret[keyName] = ToString(val) + } + return ret +} + +func ToString(val interface{}) string { + if val == nil { + return "" + } + switch val.(type) { + case float64: + return strconv.FormatFloat(val.(float64), 'f', -1, 64) + case float32: + return strconv.FormatFloat(val.(float64), 'f', -1, 64) + case int64: + return strconv.FormatInt(val.(int64), 10) + } + return fmt.Sprintf("%v", val) +} + +//兼容0x格式 +func HexDecode(in string) ([]byte, error) { + return hex.DecodeString(strings.Replace(in, "0x", "", 1)) +} diff --git a/pkg/util/snowflake.go b/pkg/util/snowflake.go new file mode 100644 index 0000000..6731336 --- /dev/null +++ b/pkg/util/snowflake.go @@ -0,0 +1,83 @@ +package util + +import ( + "errors" + "sync" + "time" +) + +// +-----------------------------------------------------------------------+ +// | UNUSED(1BIT) | TIMESTAMP(41BIT) | NODE-ID(10BIT) | SEQUENCE-ID(12BIT) | +// +-----------------------------------------------------------------------+ + +// snowflake算法,用于生成分布式系统下全局唯一id(64位) +// 1位符号位 +// 41位时间戳(毫秒级),为当前时间戳-初始时间戳,大约能使用从初始时间戳开始至初始时间戳69年后 +// 10位节点id +// 12位序列id + +const ( + nodeBits uint8 = 10 // 节点id所占的位数,10位表示最多可以有2^10=1024个节点 + sequenceBits uint8 = 12 // 序列id所占的位数,12位表示每台机器1毫秒内最多可以生成2^12=4096个唯一id + nodeMax int64 = -1 ^ (-1 << nodeBits) // 节点id的最大值,位运算用于防止溢出,默认为1023 + sequenceMax int64 = -1 ^ (-1 << sequenceBits) // 序列id的最大值,位运算用于防止溢出,默认为4095 + timestampShift uint8 = nodeBits + sequenceBits // 时间戳左移位数,默认为22 + nodeShift uint8 = sequenceBits // 节点id左移位数,默认为12 + epoch int64 = 1590940800000 // 初始时间戳(默认为2020-06-01 00:00:00的毫秒级时间戳,正式使用后不可修改) +) + +// Snowflake 定义一个Snowflake节点所需要的基本参数 +type Snowflake struct { + sync.Mutex // 互斥锁,用于确保并发安全 + timestamp int64 // 当前时间戳(毫秒级) + nodeId int64 // 当前节点的id + sequenceId int64 // 当前毫秒下已经生成的序列id,默认在1毫秒内最多生成4096个 +} + +// NewSnowflake 实例化一个Snowflake节点 +func NewSnowflake(nodeId int64, location ...*time.Location) (*Snowflake, error) { + // 判断nodeId是否非法 + if nodeId < 0 || nodeId > nodeMax { + return nil, errors.New("node id excess of quantity") + } + return &Snowflake{ + timestamp: TimeNowUnixMilli(location...), + nodeId: nodeId, + sequenceId: 0, + }, nil +} + +// NextId 获取唯一id +func (s *Snowflake) NextId(location ...*time.Location) int64 { + s.Lock() + defer s.Unlock() + + // 获取当前时间的毫秒级时间戳 + now := TimeNowUnixMilli(location...) + + // 发生时钟回拨时,等待时间重回timestamp记录的时间 + if s.timestamp > now { + for now <= s.timestamp { + SleepMilli(s.timestamp - now) + now = TimeNowUnixMilli(location...) + } + } + + if s.timestamp == now { + s.sequenceId++ + // 判断当前节点是否在1毫秒内生成了sequenceMax个id + if s.sequenceId > sequenceMax { + // 如果当前节点在1毫秒内生成的id超过了上限,则等待1毫秒后再继续生成id + for now <= s.timestamp { + SleepMilli(1) + now = TimeNowUnixMilli(location...) + } + } + } else { + // 如果当前时间戳与节点上一次生成id的时间戳不一致,则重置节点的序列id + s.sequenceId = 0 + s.timestamp = now // 将当前节点上一次生成id的时间戳更新为当前时间戳 + } + id := int64((now-epoch)< "${file_path}/${pkg_name}"/Makefile +# else +# cp "${file_path}/configs/Makefile-${file_name}" "${file_path}/${pkg_name}"/Makefile +# fi + + # cp "${file_path}"/README.md "${file_path}/${pkg_name}" + tar -zcvPf "${file_path}/${pkg_name}".tar.gz -C "${file_path}" "${pkg_name}"/etc "${pkg_name}"/bin +# "${pkg_name}"/Makefile + # rm -r "${file_path}/${pkg_name}" +} + +# 设置目标打包环境 +# 默认 linux amd64 +initOS() { + env_type="amd64" + if [ -n "$1" ]; then + env_type="$1" + fi + + export GOOS=linux + export GOARCH=${env_type} + export GOLANG_PROTOBUF_REGISTRATION_CONFLICT=warn + + echo "linux_${env_type}" +} \ No newline at end of file diff --git a/script/dtalk.lua b/script/dtalk.lua new file mode 100644 index 0000000..a46f16f --- /dev/null +++ b/script/dtalk.lua @@ -0,0 +1,67 @@ +local p_dtalk = Proto("dtalk", "dtalk layer protocal"); + +local f_data = ProtoField.bytes("dtalk.data", "Data") +local f_type = ProtoField.string("dtalk.type", "Type") + +p_dtalk.fields = { f_data,f_type } + +local protobuf_dissector = Dissector.get("protobuf") + +DissectorTable.new('dtalk_dis', 'dtalk_dis', ftypes.UINT32, base.DEC, p_dtalk) + +local msgtypes = { + [1] = 'chat33.comet.AuthMsg', + [2] = 'chat33.comet.AuthReply', + [3] = 'chat33.comet.Heartbeat', + [4] = 'chat33.comet.HeartbeatReply', + [5] = 'chat33.comet.Disconnect', + [6] = 'chat33.comet.DisconnectReply', + [7] = 'dtalk.proto.Proto', + [8] = 'dtalk.proto.Proto', + [9] = 'dtalk.proto.Proto', + [10] = 'dtalk.proto.Proto' +} + +local option_name = { + [0] = 'Undefined', + [1] = 'Auth', + [2] = 'AuthReply', + [3] = 'Heartbeat', + [4] = 'HeartbeatReply', + [5] = 'Disconnect', + [6] = 'DisconnectReply', + [7] = 'SendMsg', + [8] = 'SendMsgReply', + [9] = 'ReceiveMsg', + [10] = 'ReceiveMsgReply', + [14] = 'SyncMsgReq', + [15] = 'SyncMsgReply' +} + +--biz proto +local p_dtalk_biz = Proto("dtalk_biz", "dtalk biz layer protocal"); + +function p_dtalk.dissector(buf, pkt, tree) + local subtree = tree:add(p_dtalk, buf()) + + --Data + subtree:add(f_data, buf()) + --Type + local opt_type = tonumber(pkt.private["dtalk_opt_type"]) + subtree:add(f_type, option_name[opt_type]) + + pkt.private["pb_msg_type"] = "message," .. msgtypes[opt_type] + pcall(Dissector.call, protobuf_dissector, buf, pkt, subtree) + + + local protobuf_field_table = DissectorTable.get("protobuf_field") + protobuf_field_table:add("dtalk.proto.Proto.body", p_dtalk_biz) +end + +function p_dtalk_biz.dissector(buf, pkt, tree) + for id, name in pairs(DissectorTable.list()) do + tree:add(id .. ':', name) + end + pkt.private["pb_msg_type"] = "message," .. 'dtalk.proto.CommonMsg' + pcall(Dissector.call, protobuf_dissector, buf, pkt, tree) +end \ No newline at end of file diff --git a/script/gen-proto/protoc-gen-go-grpc_v1.1.0 b/script/gen-proto/protoc-gen-go-grpc_v1.1.0 new file mode 100644 index 0000000..97300ab Binary files /dev/null and b/script/gen-proto/protoc-gen-go-grpc_v1.1.0 differ diff --git a/script/gen-proto/protoc-gen-go_v1.27.1 b/script/gen-proto/protoc-gen-go_v1.27.1 new file mode 100644 index 0000000..5ed12cd Binary files /dev/null and b/script/gen-proto/protoc-gen-go_v1.27.1 differ diff --git a/script/gen-proto/protoc_v3.19.1 b/script/gen-proto/protoc_v3.19.1 new file mode 100644 index 0000000..bcf0afd Binary files /dev/null and b/script/gen-proto/protoc_v3.19.1 differ diff --git a/script/mysql/dtalk_app.sql b/script/mysql/dtalk_app.sql new file mode 100644 index 0000000..0f8abd0 --- /dev/null +++ b/script/mysql/dtalk_app.sql @@ -0,0 +1,210 @@ +/* + Navicat Premium Data Transfer + + Source Server : 172.16.101.107 + Source Server Type : MySQL + Source Server Version : 50732 + Source Host : 172.16.101.107:3306 + Source Schema : dtalk + + Target Server Type : MySQL + Target Server Version : 50732 + File Encoding : 65001 + + Date: 18/11/2021 10:36:53 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for dtalk_addr_backup +-- ---------------------------- +DROP TABLE IF EXISTS `dtalk_addr_backup`; +CREATE TABLE `dtalk_addr_backup` ( + `address` varchar(255) NOT NULL COMMENT '用户地址', + `area` varchar(4) DEFAULT NULL COMMENT '区号', + `phone` varchar(11) DEFAULT NULL COMMENT '手机号', + `email` varchar(30) DEFAULT NULL COMMENT '邮箱', + `mnemonic` varchar(1020) DEFAULT NULL COMMENT '助记词', + `private_key` varchar(1020) DEFAULT NULL COMMENT '加密私钥', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + PRIMARY KEY (`address`), + KEY `idx_phone` (`phone`) USING HASH, + KEY `idx_email` (`email`) USING HASH +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- ---------------------------- +-- Table structure for dtalk_addr_relate +-- ---------------------------- +DROP TABLE IF EXISTS `dtalk_addr_relate`; +CREATE TABLE `dtalk_addr_relate` ( + `address` varchar(255) NOT NULL COMMENT '用户地址', + `area` varchar(4) DEFAULT NULL COMMENT '区号', + `phone` varchar(11) DEFAULT NULL COMMENT '手机号', + `email` varchar(30) DEFAULT NULL COMMENT '邮箱', + `mnemonic` varchar(1020) DEFAULT NULL COMMENT '助记词', + `private_key` varchar(1020) DEFAULT NULL COMMENT '加密私钥', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + PRIMARY KEY (`address`), + KEY `idx_phone` (`phone`) USING HASH, + KEY `idx_email` (`email`) USING HASH +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- ---------------------------- +-- Table structure for dtalk_cdk_info +-- ---------------------------- +DROP TABLE IF EXISTS `dtalk_cdk_info`; +CREATE TABLE `dtalk_cdk_info` ( + `cdk_id` bigint(20) NOT NULL COMMENT '兑换码id', + `cdk_name` varchar(255) NOT NULL COMMENT '兑换码名称', + `cdk_info` varchar(255) DEFAULT NULL COMMENT '兑换码详情', + `coin_name` varchar(255) NOT NULL COMMENT '票券名称', + `exchange_rate` bigint(20) NOT NULL COMMENT '汇率(一个兑换码需要的票券数量)', + `create_time` bigint(20) NOT NULL COMMENT '创建时间', + `update_time` bigint(20) NOT NULL COMMENT '更新时间', + `delete_time` bigint(20) NOT NULL COMMENT '删除时间(大于零表示已删除)', + PRIMARY KEY (`cdk_id`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT; + +-- ---------------------------- +-- Table structure for dtalk_cdk_list +-- ---------------------------- +DROP TABLE IF EXISTS `dtalk_cdk_list`; +CREATE TABLE `dtalk_cdk_list` ( + `id` bigint(20) NOT NULL COMMENT '记录id', + `cdk_id` bigint(20) NOT NULL COMMENT 'cdk的id', + `cdk_content` varchar(255) NOT NULL COMMENT 'cdk的内容', + `user_id` varchar(255) DEFAULT NULL COMMENT '拥有用户id', + `cdk_status` tinyint(4) NOT NULL COMMENT 'cdk的状态(0:未发放;1:冻结;2:已发放)', + `order_id` bigint(20) DEFAULT NULL COMMENT '订单id', + `create_time` bigint(20) NOT NULL COMMENT '创建时间', + `update_time` bigint(20) NOT NULL COMMENT '更新时间', + `delete_time` bigint(20) NOT NULL COMMENT '删除时间', + `exchange_time` bigint(20) NOT NULL COMMENT '兑换时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT; + +-- ---------------------------- +-- Table structure for dtalk_group_apply +-- ---------------------------- +DROP TABLE IF EXISTS `dtalk_group_apply`; +CREATE TABLE `dtalk_group_apply` ( + `id` bigint(20) NOT NULL COMMENT '审批 ID', + `group_id` bigint(20) NOT NULL COMMENT '群 ID', + `inviter_id` varchar(40) DEFAULT NULL COMMENT '邀请人 ID, 空表示是自己主动申请的', + `member_id` varchar(40) NOT NULL COMMENT '申请加入人 ID', + `apply_note` varchar(255) DEFAULT NULL COMMENT '申请备注', + `operator_id` varchar(40) DEFAULT NULL COMMENT '审批人 ID', + `apply_status` tinyint(4) NOT NULL COMMENT '0=待审批, 1=审批通过, 2=审批不通过, 10=审批忽略', + `reject_reason` varchar(255) DEFAULT NULL COMMENT '拒绝原因', + `create_time` bigint(20) DEFAULT NULL COMMENT '创建时间 ms', + `update_time` bigint(20) DEFAULT NULL COMMENT '修改时间 ms', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- ---------------------------- +-- Table structure for dtalk_group_info +-- ---------------------------- +DROP TABLE IF EXISTS `dtalk_group_info`; +CREATE TABLE `dtalk_group_info` ( + `group_id` bigint(20) NOT NULL COMMENT '群id', + `group_mark_id` varchar(40) NOT NULL COMMENT '群编号', + `group_name` varchar(200) NOT NULL COMMENT '群名称', + `group_avatar` varchar(1000) NOT NULL DEFAULT '' COMMENT '群头像 url', + `group_member_num` int(11) NOT NULL COMMENT '群成员人数', + `group_maximum` int(11) NOT NULL DEFAULT '200' COMMENT '群成员人数上限, 默认 200 人', + `group_introduce` longtext NOT NULL COMMENT '群简介', + `group_status` tinyint(4) NOT NULL COMMENT '群状态,0=正常 1=封禁 2=解散', + `group_owner_id` varchar(40) NOT NULL COMMENT '群主 id', + `group_create_time` bigint(20) NOT NULL COMMENT '创建时间', + `group_update_time` bigint(20) NOT NULL COMMENT '更新时间', + `group_join_type` tinyint(4) NOT NULL COMMENT '加群方式,0=无需审批(默认),1=禁止加群,群主和管理员邀请加群', + `group_mute_type` tinyint(4) NOT NULL COMMENT '禁言, 0=全员可发言, 1=全员禁言(除群主和管理员)', + `group_friend_type` tinyint(4) NOT NULL COMMENT '加好友限制, 0=群内可加好友,1=群内禁止加好友', + `group_aes_key` varchar(255) DEFAULT NULL COMMENT 'aes key', + `group_pub_name` varchar(255) DEFAULT NULL COMMENT '群公开名称', + PRIMARY KEY (`group_id`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- ---------------------------- +-- Table structure for dtalk_group_member +-- ---------------------------- +DROP TABLE IF EXISTS `dtalk_group_member`; +CREATE TABLE `dtalk_group_member` ( + `group_id` bigint(20) NOT NULL COMMENT '群 id', + `group_member_id` varchar(40) NOT NULL COMMENT '用户 id', + `group_member_name` varchar(40) NOT NULL COMMENT '用户群昵称', + `group_member_type` tinyint(4) NOT NULL COMMENT '用户角色,2=群主,1=管理员,0=群员,3=退群', + `group_member_join_time` bigint(20) NOT NULL COMMENT '用户加群时间', + `group_member_update_time` bigint(20) NOT NULL COMMENT '用户更新时间', + PRIMARY KEY (`group_id`,`group_member_id`), + KEY `idx_userid_type` (`group_member_id`,`group_member_type`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- ---------------------------- +-- Table structure for dtalk_group_member_mute +-- ---------------------------- +DROP TABLE IF EXISTS `dtalk_group_member_mute`; +CREATE TABLE `dtalk_group_member_mute` ( + `group_id` bigint(20) NOT NULL COMMENT '群 id', + `group_member_id` varchar(40) NOT NULL COMMENT '用户 id', + `group_member_mute_time` bigint(20) NOT NULL COMMENT '用户禁言结束时间', + `group_member_mute_update_time` bigint(20) NOT NULL COMMENT '用户上一次被禁言的时间', + PRIMARY KEY (`group_id`,`group_member_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- ---------------------------- +-- Table structure for dtalk_oss_config +-- ---------------------------- +DROP TABLE IF EXISTS `dtalk_oss_config`; +CREATE TABLE `dtalk_oss_config` ( + `app` varchar(20) NOT NULL COMMENT '应用类型', + `oss_type` varchar(20) NOT NULL COMMENT '存储服务类型', + `endpoint` varchar(255) DEFAULT NULL COMMENT '服务节点', + `access_key_id` varchar(255) DEFAULT NULL, + `access_key_secret` varchar(255) DEFAULT NULL, + `role` varchar(255) DEFAULT NULL, + `policy` varchar(255) DEFAULT NULL COMMENT '角色权限控制', + `duration_seconds` int(11) DEFAULT NULL COMMENT '最大会话时间', + PRIMARY KEY (`app`,`oss_type`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- ---------------------------- +-- Table structure for dtalk_ver_auth +-- ---------------------------- +DROP TABLE IF EXISTS `dtalk_ver_auth`; +CREATE TABLE `dtalk_ver_auth` ( + `app_id` varchar(40) NOT NULL COMMENT 'AppId', + `app_config` text NOT NULL COMMENT '应用配置内容', + `app_key` varchar(64) NOT NULL COMMENT 'key', + `update_time` bigint(20) NOT NULL COMMENT '更新时间', + `create_time` bigint(20) NOT NULL COMMENT '创建时间', + PRIMARY KEY (`app_id`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT; + +-- ---------------------------- +-- Table structure for dtalk_ver_backend +-- ---------------------------- +DROP TABLE IF EXISTS `dtalk_ver_backend`; +CREATE TABLE `dtalk_ver_backend` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '版本编号', + `platform` varchar(40) NOT NULL COMMENT '平台', + `state` tinyint(4) NOT NULL COMMENT '线上状态', + `device_type` varchar(40) NOT NULL COMMENT '终端', + `version_code` bigint(20) NOT NULL COMMENT '版本号', + `version_name` varchar(40) NOT NULL COMMENT '版本名字', + `download_url` varchar(2083) NOT NULL COMMENT '下载地址', + `size` bigint(20) NOT NULL COMMENT '包大小', + `md5` varchar(40) NOT NULL COMMENT 'MD5', + `force_update` tinyint(4) NOT NULL COMMENT '强制更新', + `description` text NOT NULL COMMENT '描述信息', + `ope_user` varchar(40) DEFAULT NULL COMMENT '操作者', + `update_time` bigint(20) NOT NULL COMMENT '更新时间', + `create_time` bigint(20) NOT NULL COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/script/mysql/dtalk_msg.sql b/script/mysql/dtalk_msg.sql new file mode 100644 index 0000000..1f3d1c7 --- /dev/null +++ b/script/mysql/dtalk_msg.sql @@ -0,0 +1,112 @@ +/* + Navicat Premium Data Transfer + + Source Server : 172.16.101.127 + Source Server Type : MySQL + Source Server Version : 50733 + Source Host : 172.16.101.127:3306 + Source Schema : dtalk + + Target Server Type : MySQL + Target Server Version : 50733 + File Encoding : 65001 + + Date: 18/11/2021 10:37:07 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for dtalk_group_msg_content +-- ---------------------------- +DROP TABLE IF EXISTS `dtalk_group_msg_content`; +CREATE TABLE `dtalk_group_msg_content` ( + `mid` bigint(20) unsigned NOT NULL COMMENT '\n\n消息id\n', + `seq` varchar(40) NOT NULL COMMENT '消息序列号', + `sender_id` varchar(40) NOT NULL COMMENT '发送者', + `receiver_id` varchar(40) NOT NULL COMMENT '接收者', + `msg_type` tinyint(3) unsigned NOT NULL COMMENT '消息类型', + `content` longtext NOT NULL COMMENT '消息内容', + `create_time` bigint(20) NOT NULL COMMENT '创建时间', + `source` varchar(1024) DEFAULT NULL COMMENT '转发来源', + PRIMARY KEY (`mid`) USING BTREE, + KEY `idx_sender_id_seq` (`sender_id`,`seq`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; + +-- ---------------------------- +-- Table structure for dtalk_group_msg_relation +-- ---------------------------- +DROP TABLE IF EXISTS `dtalk_group_msg_relation`; +CREATE TABLE `dtalk_group_msg_relation` ( + `mid` bigint(20) unsigned NOT NULL COMMENT '消息id', + `owner_uid` varchar(40) NOT NULL COMMENT '索引用户', + `other_uid` varchar(40) NOT NULL COMMENT '\n\n另一方用户\n', + `type` tinyint(3) unsigned NOT NULL COMMENT '0->发件箱;1->收件箱', + `state` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '0->未接受;1->已接收', + `create_time` bigint(20) NOT NULL COMMENT '\n\n创建时间\n', + PRIMARY KEY (`mid`,`owner_uid`) USING BTREE, + KEY `idx_owneruid_otheruid_msgid` (`owner_uid`,`other_uid`,`mid`) USING BTREE, + KEY `idx_owneruid_type_state` (`owner_uid`,`type`,`state`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; + +-- ---------------------------- +-- Table structure for dtalk_msg_content +-- ---------------------------- +DROP TABLE IF EXISTS `dtalk_msg_content`; +CREATE TABLE `dtalk_msg_content` ( + `mid` bigint(20) unsigned NOT NULL COMMENT '\n\n消息id\n', + `seq` varchar(40) NOT NULL COMMENT '消息序列号', + `sender_id` varchar(40) NOT NULL COMMENT '发送者', + `receiver_id` varchar(40) NOT NULL COMMENT '接收者', + `msg_type` tinyint(3) unsigned NOT NULL COMMENT '消息类型', + `content` longtext NOT NULL COMMENT '消息内容', + `create_time` bigint(20) NOT NULL COMMENT '创建时间', + `source` varchar(1024) DEFAULT NULL COMMENT '转发来源', + PRIMARY KEY (`mid`) USING BTREE, + KEY `idx_sender_id_seq` (`sender_id`,`seq`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; + +-- ---------------------------- +-- Table structure for dtalk_msg_relation +-- ---------------------------- +DROP TABLE IF EXISTS `dtalk_msg_relation`; +CREATE TABLE `dtalk_msg_relation` ( + `mid` bigint(20) unsigned NOT NULL COMMENT '消息id', + `owner_uid` varchar(40) NOT NULL COMMENT '索引用户', + `other_uid` varchar(40) NOT NULL COMMENT '\n\n另一方用户\n', + `type` tinyint(3) unsigned NOT NULL COMMENT '0->发件箱;1->收件箱', + `state` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '0->未接受;1->已接收', + `create_time` bigint(20) NOT NULL COMMENT '\n\n创建时间\n', + PRIMARY KEY (`mid`,`owner_uid`) USING BTREE, + KEY `idx_owneruid_otheruid_msgid` (`owner_uid`,`other_uid`,`mid`) USING BTREE, + KEY `idx_owneruid_type_state` (`owner_uid`,`type`,`state`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; + +-- ---------------------------- +-- Table structure for dtalk_msg_version +-- ---------------------------- +DROP TABLE IF EXISTS `dtalk_msg_version`; +CREATE TABLE `dtalk_msg_version` ( + `uid` varchar(40) NOT NULL COMMENT '\n\n用户id\n', + `version` bigint(20) DEFAULT NULL COMMENT '版本号', + PRIMARY KEY (`uid`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; + +-- ---------------------------- +-- Table structure for dtalk_notice_content +-- ---------------------------- +DROP TABLE IF EXISTS `dtalk_notice_content`; +CREATE TABLE `dtalk_notice_content` ( + `id` bigint(20) NOT NULL COMMENT '消息id', + `uid` varchar(40) NOT NULL COMMENT '接收者', + `type` tinyint(3) DEFAULT NULL COMMENT '通知类型', + `state` tinyint(3) DEFAULT NULL COMMENT '0->未接收;1->已接收', + `content` varchar(1024) DEFAULT NULL COMMENT '通知内容', + `create_time` bigint(20) DEFAULT NULL COMMENT '创建时间', + `update_time` bigint(20) DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`,`uid`) USING BTREE, + KEY `idx_uid_state` (`uid`,`state`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/script/swag/build.sh b/script/swag/build.sh new file mode 100644 index 0000000..e9de64a --- /dev/null +++ b/script/swag/build.sh @@ -0,0 +1,17 @@ +#!/bin/bash +file_path=$( + cd "$(dirname "$0")" || exit + pwd +)/../.. + +echo $file_path + +#projectPath = $(dirname $(dirname "$PWD")) + +if [ -z $1 ] +then + echo 'ERROR: undefined version + please input gateway version, example: ./build.sh v1' +else + swag init -d $file_path/gateway/api/$1/ -g internal/handler/routes.go -o $file_path/gateway/api/$1/docs/ +fi \ No newline at end of file diff --git a/script/trace/docker-start-jaeger.sh b/script/trace/docker-start-jaeger.sh new file mode 100644 index 0000000..84c1ea2 --- /dev/null +++ b/script/trace/docker-start-jaeger.sh @@ -0,0 +1,11 @@ +docker run -d --name jaeger \ + -e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \ + -p 5775:5775/udp \ + -p 6831:6831/udp \ + -p 6832:6832/udp \ + -p 5778:5778 \ + -p 16686:16686 \ + -p 14268:14268 \ + -p 14250:14250 \ + -p 9411:9411 \ + jaegertracing/all-in-one:1.29 diff --git a/service/auth/CHANGELOG.md b/service/auth/CHANGELOG.md new file mode 100644 index 0000000..1768f85 --- /dev/null +++ b/service/auth/CHANGELOG.md @@ -0,0 +1,36 @@ +版本号`major.minor.patch`具体规则如下: +- major:主版本号,如有重大版本重构则该字段递增,通常各主版本间接口不兼容。 +- minor:次版本号,各次版本号间接口保持兼容,如有接口新增或优化则该字段递增。 +- patch:补丁号,如有功能改善或缺陷修复则该字段递增。 + + +## version 0.0.1 @2021.7.2 + +**Feature** + +创建 auth 服务 +- Appid 的注册 +- Auth 的基本实现 + + + + +## version x.x.x @YY.mm.dd + +example + +**Feature** + +- 增加了什么接口 + +**Bug Fixes** + +- 修复了什么 bug + +**Improvement** + +- 重大更新, 增加字段, 与旧版本不兼容 + +**Breaking Change** + +- 弃用了什么 diff --git a/service/auth/Makefile b/service/auth/Makefile new file mode 100644 index 0000000..2266980 --- /dev/null +++ b/service/auth/Makefile @@ -0,0 +1,37 @@ +# golang1.9 or latest +# 1. make help +# 2. make dep +# 3. make build +# ... + +VERSION := $(shell echo $(shell cat version.go | grep "Version =" | cut -d '=' -f2)) +APP_NAME := auth +BUILD_DIR := build +APP := ${BUILD_DIR}/${APP_NAME} +PKG_NAME := ${APP_NAME}_v${VERSION} +PKG := ${PKG_NAME}.tar.gz + +LDFLAGS := -ldflags "-w -s -X gitlab.33.cn/chat/dtalk/service/$(APP_NAME).GitCommit=`git rev-parse --short=8 HEAD`" +LDGRPC := -ldflags "-X google.golang.org/protobuf/reflect/protoregistry.conflictPolicy=warn" + +.PHONY: clean build pkg + +clean: ## Remove previous build + @rm -rf ${BUILD_DIR} + @go clean + +build: #checkgofmt ## Build the binary file + swag init -g server/http/http.go + GOOS=linux GOARCH=amd64 GO111MODULE=on GOPROXY=https://goproxy.cn,direct GOSUMDB="sum.golang.google.cn" go build -v $(LDGRPC) $(LDFLAGS) -o $(APP) cmd/main.go + +pkg: build ## Package + mkdir -p ${PKG_NAME}/bin + mkdir -p ${PKG_NAME}/etc + cp ${APP} ${PKG_NAME}/bin/ + cp config/$(APP_NAME).toml ${PKG_NAME}/etc/ + tar zvcf ${PKG} ${PKG_NAME} + rm -rf ${PKG_NAME} + +run: + swag init -g server/http/http.go + go run $(LDGRPC) $(LDFLAGS) cmd/main.go -conf config/$(APP_NAME).toml \ No newline at end of file diff --git a/service/auth/api/auth.pb.go b/service/auth/api/auth.pb.go new file mode 100644 index 0000000..f6be9da --- /dev/null +++ b/service/auth/api/auth.pb.go @@ -0,0 +1,223 @@ +// protoc -I=. -I=$GOPATH/src --go_out=plugins=grpc:. *.proto + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc v3.19.1 +// source: auth.proto + +package auth + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type AuthRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Appid string `protobuf:"bytes,1,opt,name=appid,proto3" json:"appid,omitempty"` + Token string `protobuf:"bytes,2,opt,name=token,proto3" json:"token,omitempty"` +} + +func (x *AuthRequest) Reset() { + *x = AuthRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_auth_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AuthRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AuthRequest) ProtoMessage() {} + +func (x *AuthRequest) ProtoReflect() protoreflect.Message { + mi := &file_auth_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AuthRequest.ProtoReflect.Descriptor instead. +func (*AuthRequest) Descriptor() ([]byte, []int) { + return file_auth_proto_rawDescGZIP(), []int{0} +} + +func (x *AuthRequest) GetAppid() string { + if x != nil { + return x.Appid + } + return "" +} + +func (x *AuthRequest) GetToken() string { + if x != nil { + return x.Token + } + return "" +} + +type AuthReply struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Uid string `protobuf:"bytes,1,opt,name=uid,proto3" json:"uid,omitempty"` +} + +func (x *AuthReply) Reset() { + *x = AuthReply{} + if protoimpl.UnsafeEnabled { + mi := &file_auth_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AuthReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AuthReply) ProtoMessage() {} + +func (x *AuthReply) ProtoReflect() protoreflect.Message { + mi := &file_auth_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AuthReply.ProtoReflect.Descriptor instead. +func (*AuthReply) Descriptor() ([]byte, []int) { + return file_auth_proto_rawDescGZIP(), []int{1} +} + +func (x *AuthReply) GetUid() string { + if x != nil { + return x.Uid + } + return "" +} + +var File_auth_proto protoreflect.FileDescriptor + +var file_auth_proto_rawDesc = []byte{ + 0x0a, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0a, 0x64, 0x74, + 0x61, 0x6c, 0x6b, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x22, 0x39, 0x0a, 0x0b, 0x41, 0x75, 0x74, 0x68, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x70, 0x70, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x70, 0x70, 0x69, 0x64, 0x12, 0x14, 0x0a, + 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, + 0x6b, 0x65, 0x6e, 0x22, 0x1d, 0x0a, 0x09, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x70, 0x6c, 0x79, + 0x12, 0x10, 0x0a, 0x03, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, + 0x69, 0x64, 0x32, 0x40, 0x0a, 0x04, 0x41, 0x75, 0x74, 0x68, 0x12, 0x38, 0x0a, 0x06, 0x44, 0x6f, + 0x41, 0x75, 0x74, 0x68, 0x12, 0x17, 0x2e, 0x64, 0x74, 0x61, 0x6c, 0x6b, 0x2e, 0x61, 0x75, 0x74, + 0x68, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, + 0x64, 0x74, 0x61, 0x6c, 0x6b, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x52, + 0x65, 0x70, 0x6c, 0x79, 0x42, 0x26, 0x5a, 0x24, 0x67, 0x69, 0x74, 0x6c, 0x61, 0x62, 0x2e, 0x33, + 0x33, 0x2e, 0x63, 0x6e, 0x2f, 0x63, 0x68, 0x61, 0x74, 0x2f, 0x64, 0x74, 0x61, 0x6c, 0x6b, 0x2f, + 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_auth_proto_rawDescOnce sync.Once + file_auth_proto_rawDescData = file_auth_proto_rawDesc +) + +func file_auth_proto_rawDescGZIP() []byte { + file_auth_proto_rawDescOnce.Do(func() { + file_auth_proto_rawDescData = protoimpl.X.CompressGZIP(file_auth_proto_rawDescData) + }) + return file_auth_proto_rawDescData +} + +var file_auth_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_auth_proto_goTypes = []interface{}{ + (*AuthRequest)(nil), // 0: dtalk.auth.AuthRequest + (*AuthReply)(nil), // 1: dtalk.auth.AuthReply +} +var file_auth_proto_depIdxs = []int32{ + 0, // 0: dtalk.auth.Auth.DoAuth:input_type -> dtalk.auth.AuthRequest + 1, // 1: dtalk.auth.Auth.DoAuth:output_type -> dtalk.auth.AuthReply + 1, // [1:2] is the sub-list for method output_type + 0, // [0:1] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_auth_proto_init() } +func file_auth_proto_init() { + if File_auth_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_auth_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AuthRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_auth_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AuthReply); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_auth_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_auth_proto_goTypes, + DependencyIndexes: file_auth_proto_depIdxs, + MessageInfos: file_auth_proto_msgTypes, + }.Build() + File_auth_proto = out.File + file_auth_proto_rawDesc = nil + file_auth_proto_goTypes = nil + file_auth_proto_depIdxs = nil +} diff --git a/service/auth/api/auth.proto b/service/auth/api/auth.proto new file mode 100644 index 0000000..2c2fb99 --- /dev/null +++ b/service/auth/api/auth.proto @@ -0,0 +1,18 @@ +// protoc -I=. -I=$GOPATH/src --go_out=plugins=grpc:. *.proto +syntax = "proto3"; + +package dtalk.auth; +option go_package = "gitlab.33.cn/chat/dtalk/service/auth"; + +message AuthRequest{ + string appid = 1; + string token = 2; +} + +message AuthReply { + string uid = 1; +} + +service Auth { + rpc DoAuth(AuthRequest) returns (AuthReply); +} diff --git a/service/auth/api/auth_grpc.pb.go b/service/auth/api/auth_grpc.pb.go new file mode 100644 index 0000000..3b1773e --- /dev/null +++ b/service/auth/api/auth_grpc.pb.go @@ -0,0 +1,101 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. + +package auth + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// AuthClient is the client API for Auth service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type AuthClient interface { + DoAuth(ctx context.Context, in *AuthRequest, opts ...grpc.CallOption) (*AuthReply, error) +} + +type authClient struct { + cc grpc.ClientConnInterface +} + +func NewAuthClient(cc grpc.ClientConnInterface) AuthClient { + return &authClient{cc} +} + +func (c *authClient) DoAuth(ctx context.Context, in *AuthRequest, opts ...grpc.CallOption) (*AuthReply, error) { + out := new(AuthReply) + err := c.cc.Invoke(ctx, "/dtalk.auth.Auth/DoAuth", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// AuthServer is the server API for Auth service. +// All implementations must embed UnimplementedAuthServer +// for forward compatibility +type AuthServer interface { + DoAuth(context.Context, *AuthRequest) (*AuthReply, error) + mustEmbedUnimplementedAuthServer() +} + +// UnimplementedAuthServer must be embedded to have forward compatible implementations. +type UnimplementedAuthServer struct { +} + +func (UnimplementedAuthServer) DoAuth(context.Context, *AuthRequest) (*AuthReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method DoAuth not implemented") +} +func (UnimplementedAuthServer) mustEmbedUnimplementedAuthServer() {} + +// UnsafeAuthServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to AuthServer will +// result in compilation errors. +type UnsafeAuthServer interface { + mustEmbedUnimplementedAuthServer() +} + +func RegisterAuthServer(s grpc.ServiceRegistrar, srv AuthServer) { + s.RegisterService(&Auth_ServiceDesc, srv) +} + +func _Auth_DoAuth_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AuthRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthServer).DoAuth(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.auth.Auth/DoAuth", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthServer).DoAuth(ctx, req.(*AuthRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// Auth_ServiceDesc is the grpc.ServiceDesc for Auth service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Auth_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "dtalk.auth.Auth", + HandlerType: (*AuthServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "DoAuth", + Handler: _Auth_DoAuth_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "auth.proto", +} diff --git a/service/auth/api/client.go b/service/auth/api/client.go new file mode 100644 index 0000000..a3079e6 --- /dev/null +++ b/service/auth/api/client.go @@ -0,0 +1,42 @@ +package auth + +import ( + "context" + "fmt" + "gitlab.33.cn/chat/dtalk/pkg/naming" + "gitlab.33.cn/chat/dtalk/pkg/net/grpc" + "google.golang.org/grpc/resolver" + "time" +) + +type Client struct { + client AuthClient +} + +func New(etcdAddr, schema, srvName string, dial time.Duration) *Client { + rb := naming.NewResolver(etcdAddr, schema) + resolver.Register(rb) + + addr := fmt.Sprintf("%s:///%s", schema, srvName) // "schema://[authority]/service" + fmt.Println("group rpc client call addr:", addr) + + conn, err := grpc.NewGRPCConn(addr, dial) + if err != nil { + panic(err) + } + return &Client{ + client: NewAuthClient(conn), + } +} + +func (c *Client) DoAuth(ctx context.Context, appId string, token string) (string, error) { + request := &AuthRequest{ + Appid: appId, + Token: token, + } + reply, err := c.client.DoAuth(ctx, request) + if err != nil { + return "", err + } + return reply.Uid, nil +} diff --git a/service/auth/api/create.sh b/service/auth/api/create.sh new file mode 100644 index 0000000..0e5dbf9 --- /dev/null +++ b/service/auth/api/create.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +protoc -I. -I$GOPATH/src \ + --go_out=. --go_opt=paths=source_relative \ + --go-grpc_out=. --go-grpc_opt=paths=source_relative \ + *.proto \ No newline at end of file diff --git a/service/auth/cmd/main.go b/service/auth/cmd/main.go new file mode 100644 index 0000000..13af7ac --- /dev/null +++ b/service/auth/cmd/main.go @@ -0,0 +1,83 @@ +package main + +import ( + "context" + "flag" + "fmt" + "github.com/Terry-Mao/goim/pkg/ip" + "gitlab.33.cn/chat/dtalk/pkg/naming" + "gitlab.33.cn/chat/dtalk/service/auth/server/grpc" + "net" + "os" + "os/signal" + "syscall" + "time" + + "github.com/inconshreveable/log15" + "gitlab.33.cn/chat/dtalk/service/auth/config" + "gitlab.33.cn/chat/dtalk/service/auth/server/http" + "gitlab.33.cn/chat/dtalk/service/auth/service" +) + +const srvName = "auth" + +var log = log15.New("cmd", srvName) + +func main() { + flag.Parse() + if err := config.Init(); err != nil { + panic(err) + } + log.Info("config info:", + "Server", *config.Conf.Server, + "reg", *config.Conf.Reg, + ) + // service init + svc := service.New(config.Conf) + httpSrv := http.Init(svc) + + // rpc init + rpc := grpc.New(config.Conf.GRPCServer, svc) + // register server + _, port, _ := net.SplitHostPort(config.Conf.GRPCServer.Addr) + addr := fmt.Sprintf("%s:%s", ip.InternalIP(), port) + if err := naming.Register(config.Conf.Reg.RegAddrs, config.Conf.Reg.SrvName, addr, config.Conf.Reg.Schema, 15); err != nil { + panic(err) + } + fmt.Println("register ok") + + // init signal + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT) + for { + s := <-c + log.Info("service get a signal %s", s.String()) + switch s { + case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT: + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + // etcd 删除 kv + log.Info("exit", "", "etcd 删除 kv") + if err := naming.UnRegister(config.Conf.Reg.SrvName, addr, config.Conf.Reg.Schema); err != nil { + log.Error("naming.UnRegister:", "err", err) + } + // 关闭 grpc + log.Info("exit", "", "关闭 grpc") + if err := rpc.Shutdown(ctx); err != nil { + log.Error("rpc Shutdown:", "err", err) + } + // 关闭 http + log.Info("exit", "", "关闭 http") + if err := httpSrv.Shutdown(ctx); err != nil { + log.Error("server shutdown:", "err", err) + } + time.Sleep(time.Second * 2) + log.Info(srvName + " server exit") + return + case syscall.SIGHUP: + // TODO reload + default: + return + } + } +} diff --git a/service/auth/config/auth.toml b/service/auth/config/auth.toml new file mode 100644 index 0000000..577491e --- /dev/null +++ b/service/auth/config/auth.toml @@ -0,0 +1,29 @@ +Env = "debug" + +[server] +addr = "0.0.0.0:18103" + +[MySQL] +Host = "127.0.0.1" +Port = 3306 +User = "root" +Pwd = "root" +Db = "dtalk" + +[Reg] +Schema = "dtalk" +SrvName = "auth" +RegAddrs = "127.0.0.1:2379" + +[GRPCServer] +Network = "tcp" +Addr = ":18104" +Timeout = "1s" +KeepAliveMaxConnectionIdle = "60s" +KeepAliveMaxConnectionAge = "2h" +KeepAliveMaxMaxConnectionAgeGrace = "20s" +KeepAliveTime = "60s" +KeepAliveTimeout = "20s" + +[Key] +KeyExpireDuration = 60000000000 \ No newline at end of file diff --git a/service/auth/config/config.go b/service/auth/config/config.go new file mode 100644 index 0000000..f873eec --- /dev/null +++ b/service/auth/config/config.go @@ -0,0 +1,84 @@ +package config + +import ( + "flag" + "github.com/BurntSushi/toml" + "gitlab.33.cn/chat/dtalk/pkg/net/grpc" + xgrpc "gitlab.33.cn/chat/dtalk/pkg/net/grpc" + xtime "gitlab.33.cn/chat/dtalk/pkg/time" + "time" +) + +var ( + confPath string + + Conf *Config +) + +func init() { + flag.StringVar(&confPath, "conf", "auth.toml", "default config path.") +} + +// Init init config. +func Init() (err error) { + Conf = Default() + _, err = toml.DecodeFile(confPath, &Conf) + return +} + +func Default() *Config { + return &Config{ + Env: "debug", + Server: &HttpServer{ + Addr: "127.0.0.1:8080", + }, + Reg: &Reg{ + Schema: "dtalk", + SrvName: "auth", + RegAddrs: "127.0.0.1:2379", + }, + GRPCServer: &xgrpc.ServerConfig{ + Network: "tcp", + Addr: ":18012", + Timeout: xtime.Duration(time.Second), + KeepAliveMaxConnectionIdle: xtime.Duration(time.Second * 60), + KeepAliveMaxConnectionAge: xtime.Duration(time.Hour * 2), + KeepAliveMaxMaxConnectionAgeGrace: xtime.Duration(time.Second * 20), + KeepAliveTime: xtime.Duration(time.Second * 60), + KeepAliveTimeout: xtime.Duration(time.Second * 20), + }, + } +} + +type Config struct { + Env string + Server *HttpServer + MySQL *MySQL + Reg *Reg + //gRPC server + GRPCServer *grpc.ServerConfig + Key *Key +} + +type HttpServer struct { + Addr string +} + +type MySQL struct { + Host string + Port int32 + User string + Pwd string + Db string +} + +// Reg is service register/discovery config +type Reg struct { + Schema string + SrvName string // call + RegAddrs string // etcd addrs, seperate by ',' +} + +type Key struct { + KeyExpireDuration time.Duration +} diff --git a/service/auth/config/example.yml b/service/auth/config/example.yml new file mode 100644 index 0000000..d137442 --- /dev/null +++ b/service/auth/config/example.yml @@ -0,0 +1,21 @@ +#基础配置 +basic_config: + #请求方式(POST/GET) + method: POST + + #请求目标url + url: http://127.0.0.1:18002/user/login + +#请求内容 +request: + #token字段的名称(token位于header中) + token_name: FZM-SIGNATURE + +#响应内容(响应的body内容应为json格式) +response: + #uid的字段名 + uid_name: address + + #判断鉴权通过的关键字段的内容(字段内容要唯一) + success_result: + result: 0 \ No newline at end of file diff --git a/service/auth/dao/dao.go b/service/auth/dao/dao.go new file mode 100644 index 0000000..d6b02d2 --- /dev/null +++ b/service/auth/dao/dao.go @@ -0,0 +1,31 @@ +package dao + +import ( + "fmt" + "github.com/inconshreveable/log15" + "gitlab.33.cn/chat/dtalk/pkg/mysql" + "gitlab.33.cn/chat/dtalk/service/auth/config" +) + +type Dao struct { + log log15.Logger + conn *mysql.MysqlConn +} + +func New(c *config.Config) *Dao { + d := &Dao{ + log: log15.New("module", "auth/dao"), + conn: newDB(c.MySQL), + } + return d +} + +func newDB(cfg *config.MySQL) *mysql.MysqlConn { + c, err := mysql.NewMysqlConn(cfg.Host, fmt.Sprintf("%v", cfg.Port), + cfg.User, cfg.Pwd, cfg.Db, "UTF8MB4") + if err != nil { + log15.Error("mysql init failed", "err", err) + panic(err) + } + return c +} diff --git a/service/auth/dao/db.go b/service/auth/dao/db.go new file mode 100644 index 0000000..21aab8f --- /dev/null +++ b/service/auth/dao/db.go @@ -0,0 +1,43 @@ +package dao + +const ( + _InsertSignInInfo = `INSERT IGNORE INTO dtalk_ver_auth ( app_id, app_config, app_key, create_time, update_time)VALUES( ?, ?, ?, ?, ?)` + _LoadConfig = `SELECT app_config FROM dtalk_ver_auth WHERE app_id=?` + _GetKey = `SELECT app_key FROM dtalk_ver_auth WHERE app_id=?` +) + +func (d *Dao) SignIn(appId string, config *string, key string, createTime, updateTime int64) (int64, error) { + num, _, err := d.conn.Exec(_InsertSignInInfo, appId, *config, key, createTime, updateTime) + if err != nil { + return num, err + } + + return num, nil +} + +func (d *Dao) LoadConfig(appId string) (string, error) { + records, err := d.conn.Query(_LoadConfig, appId) + if err != nil { + return "", err + } + if len(records) == 0 { + return "", nil + } + + config := records[0]["app_config"] + + return config, nil +} + +func (d *Dao) GetKey(appId string) (string, error) { + records, err := d.conn.Query(_GetKey, appId) + if err != nil { + return "", err + } + if len(records) == 0 { + return "", nil + } + key := records[0]["app_key"] + + return key, nil +} diff --git a/service/auth/docs/docs.go b/service/auth/docs/docs.go new file mode 100644 index 0000000..a27095b --- /dev/null +++ b/service/auth/docs/docs.go @@ -0,0 +1,153 @@ +// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT +// This file was generated by swaggo/swag + +package docs + +import ( + "bytes" + "encoding/json" + "strings" + + "github.com/alecthomas/template" + "github.com/swaggo/swag" +) + +var doc = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{.Description}}", + "title": "{{.Title}}", + "contact": {}, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/auth/sign-in": { + "post": { + "consumes": [ + "multipart/form-data" + ], + "tags": [ + "backend" + ], + "summary": "注册一个新的 AppId", + "parameters": [ + { + "type": "file", + "description": "file", + "name": "file", + "in": "formData", + "required": true + }, + { + "type": "string", + "name": "appId", + "in": "formData" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/model.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.SignInResponse" + } + } + } + ] + } + } + } + } + } + }, + "definitions": { + "model.GeneralResponse": { + "type": "object", + "properties": { + "code": { + "type": "integer" + }, + "data": { + "type": "object" + }, + "message": { + "type": "integer" + } + } + }, + "model.SignInResponse": { + "type": "object", + "properties": { + "appId": { + "type": "string" + }, + "createTime": { + "type": "integer" + }, + "key": { + "type": "string" + }, + "updateTime": { + "type": "integer" + } + } + } + } +}` + +type swaggerInfo struct { + Version string + Host string + BasePath string + Schemes []string + Title string + Description string +} + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = swaggerInfo{ + Version: "0.0.1", + Host: "127.0.0.1:18103", + BasePath: "", + Schemes: []string{}, + Title: "auth服务接口", + Description: "", +} + +type s struct{} + +func (s *s) ReadDoc() string { + sInfo := SwaggerInfo + sInfo.Description = strings.Replace(sInfo.Description, "\n", "\\n", -1) + + t, err := template.New("swagger_info").Funcs(template.FuncMap{ + "marshal": func(v interface{}) string { + a, _ := json.Marshal(v) + return string(a) + }, + }).Parse(doc) + if err != nil { + return doc + } + + var tpl bytes.Buffer + if err := t.Execute(&tpl, sInfo); err != nil { + return doc + } + + return tpl.String() +} + +func init() { + swag.Register(swag.Name, &s{}) +} diff --git a/service/auth/docs/swagger.json b/service/auth/docs/swagger.json new file mode 100644 index 0000000..1c9fd1f --- /dev/null +++ b/service/auth/docs/swagger.json @@ -0,0 +1,89 @@ +{ + "swagger": "2.0", + "info": { + "title": "auth服务接口", + "contact": {}, + "version": "0.0.1" + }, + "host": "127.0.0.1:18103", + "paths": { + "/auth/sign-in": { + "post": { + "consumes": [ + "multipart/form-data" + ], + "tags": [ + "backend" + ], + "summary": "注册一个新的 AppId", + "parameters": [ + { + "type": "file", + "description": "file", + "name": "file", + "in": "formData", + "required": true + }, + { + "type": "string", + "name": "appId", + "in": "formData" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/model.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.SignInResponse" + } + } + } + ] + } + } + } + } + } + }, + "definitions": { + "model.GeneralResponse": { + "type": "object", + "properties": { + "code": { + "type": "integer" + }, + "data": { + "type": "object" + }, + "message": { + "type": "integer" + } + } + }, + "model.SignInResponse": { + "type": "object", + "properties": { + "appId": { + "type": "string" + }, + "createTime": { + "type": "integer" + }, + "key": { + "type": "string" + }, + "updateTime": { + "type": "integer" + } + } + } + } +} \ No newline at end of file diff --git a/service/auth/docs/swagger.yaml b/service/auth/docs/swagger.yaml new file mode 100644 index 0000000..e4c6568 --- /dev/null +++ b/service/auth/docs/swagger.yaml @@ -0,0 +1,54 @@ +definitions: + model.GeneralResponse: + properties: + code: + type: integer + data: + type: object + message: + type: integer + type: object + model.SignInResponse: + properties: + appId: + type: string + createTime: + type: integer + key: + type: string + updateTime: + type: integer + type: object +host: 127.0.0.1:18103 +info: + contact: {} + title: auth服务接口 + version: 0.0.1 +paths: + /auth/sign-in: + post: + consumes: + - multipart/form-data + parameters: + - description: file + in: formData + name: file + required: true + type: file + - in: formData + name: appId + type: string + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/model.GeneralResponse' + - properties: + data: + $ref: '#/definitions/model.SignInResponse' + type: object + summary: 注册一个新的 AppId + tags: + - backend +swagger: "2.0" diff --git a/service/auth/model/app_config.go b/service/auth/model/app_config.go new file mode 100644 index 0000000..db3d86a --- /dev/null +++ b/service/auth/model/app_config.go @@ -0,0 +1,23 @@ +package model + +type AppConfig struct { + BasicConfig BasicConfig `yaml:"basic_config"` + Request Request `yaml:"request"` + Response Response `yaml:"response"` +} + +type BasicConfig struct { + Method string `yaml:"method"` + Url string `yaml:"url"` +} + +type Request struct { + TokenName string `yaml:"token_name"` + //FieldsOfHeader []string `yaml:"fields_of_header"` + //FieldsOfBody map[interface{}]interface{} `yaml:"fields_of_body"` +} +type Response struct { + //ContentType string `yaml:"content_type"` + UidName string `yaml:"uid_name"` + SuccessResult map[interface{}]interface{} `yaml:"success_result"` +} diff --git a/service/auth/model/auth.go b/service/auth/model/auth.go new file mode 100644 index 0000000..e73668e --- /dev/null +++ b/service/auth/model/auth.go @@ -0,0 +1,8 @@ +package model + +type AuthInfo struct { + AppId string + Token string + Digest string + CreateTime int64 +} diff --git a/service/auth/model/const.go b/service/auth/model/const.go new file mode 100644 index 0000000..8380846 --- /dev/null +++ b/service/auth/model/const.go @@ -0,0 +1,10 @@ +package model + +import "time" + +const ( + Chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + Hex = "0123456789abcdef" + Length = 64 + Timeout = 20 * time.Second +) diff --git a/service/auth/model/error.go b/service/auth/model/error.go new file mode 100644 index 0000000..3547a4c --- /dev/null +++ b/service/auth/model/error.go @@ -0,0 +1,21 @@ +package model + +import "github.com/pkg/errors" + +var ( + ErrAuthFiled = errors.New("auth uid failed") + ErrFindUid = errors.New("Failed to find the uid") + ErrWrongDataFormat = errors.New("The data format is wrong") + ErrFieldIsNonexistentInCompareValues = errors.New("CompareValues: field does not exist") + //ErrFieldIsNonexistentInGenerateBodyOfRequest = errors.New("GenerateBodyOfRequest: field does not exist") + ErrParseConfig = errors.New("Failed to parse the configuration") + ErrUnmarshalBodyOfResponse = errors.New("Failed to unmarshal the body of the response") + ErrInvalidRequest = errors.New("The request is invalid") + ErrCheckDigest = errors.New("Failed to check the digest") + ErrQueryKey = errors.New("Failed to query the key") + ErrKeyIsNonexistent = errors.New("The key does not exist") + ErrLoadConfig = errors.New("Failed to query the configuration") + ErrConfigurationIsNonexistent = errors.New("The configuration does not exist") + ErrHTTPCommunication = errors.New("A HTTP communication error has occurred") + ErrInvalidToken = errors.New("The token is Invalid") +) diff --git a/service/auth/model/request.go b/service/auth/model/request.go new file mode 100644 index 0000000..974c29e --- /dev/null +++ b/service/auth/model/request.go @@ -0,0 +1,13 @@ +package model + +import "mime/multipart" + +type SignInRequest struct { + AppId string `json:"appId" form:"appId"` + ConfigFile *multipart.FileHeader `json:"-" form:"-"` +} + +type AuthRequest struct { + AppId string `json:"appId"` + Token string `json:"token"` +} diff --git a/service/auth/model/response.go b/service/auth/model/response.go new file mode 100644 index 0000000..aa53ff0 --- /dev/null +++ b/service/auth/model/response.go @@ -0,0 +1,18 @@ +package model + +type GeneralResponse struct { + Code int `json:"code"` + Message int `json:"message"` + Data interface{} `json:"data"` +} + +type SignInResponse struct { + AppId string `json:"appId"` + Key string `json:"key"` + CreateTime int64 `json:"createTime"` + UpdateTime int64 `json:"updateTime"` +} + +type AuthResponse struct { + Uid string `json:"uid"` +} diff --git a/service/auth/model/sign_in.go b/service/auth/model/sign_in.go new file mode 100644 index 0000000..f17b14a --- /dev/null +++ b/service/auth/model/sign_in.go @@ -0,0 +1,10 @@ +package model + +import "mime/multipart" + +type SignInInfo struct { + AppId string + ConfigFile *multipart.FileHeader + CreateTime int64 + UpdateTime int64 +} diff --git a/service/auth/server/grpc/server.go b/service/auth/server/grpc/server.go new file mode 100644 index 0000000..1011880 --- /dev/null +++ b/service/auth/server/grpc/server.go @@ -0,0 +1,42 @@ +package grpc + +import ( + "context" + "gitlab.33.cn/chat/dtalk/service/auth/model" + "time" + + xgrpc "gitlab.33.cn/chat/dtalk/pkg/net/grpc" + pb "gitlab.33.cn/chat/dtalk/service/auth/api" + "gitlab.33.cn/chat/dtalk/service/auth/service" + "google.golang.org/grpc" +) + +func New(cfg *xgrpc.ServerConfig, svr *service.Service) *xgrpc.Server { + connectionTimeout := grpc.ConnectionTimeout(time.Duration(cfg.Timeout)) + ws := xgrpc.NewServer(cfg, connectionTimeout) + pb.RegisterAuthServer(ws.Server(), &server{svr: svr}) + ws, err := ws.Start() + if err != nil { + panic(err) + } + return ws +} + +type server struct { + pb.UnimplementedAuthServer + svr *service.Service +} + +func (s *server) DoAuth(ctx context.Context, in *pb.AuthRequest) (*pb.AuthReply, error) { + req := &model.AuthRequest{ + AppId: in.Appid, + Token: in.Token, + } + reply, err := s.svr.Auth(req) + if err != nil { + return &pb.AuthReply{}, err + } + return &pb.AuthReply{ + Uid: reply.Uid, + }, nil +} diff --git a/service/auth/server/http/auth.go b/service/auth/server/http/auth.go new file mode 100644 index 0000000..2bb854d --- /dev/null +++ b/service/auth/server/http/auth.go @@ -0,0 +1,21 @@ +package http + +import ( + "github.com/gin-gonic/gin" + "gitlab.33.cn/chat/dtalk/pkg/api" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/service/auth/model" +) + +func Auth(c *gin.Context) { + request := model.AuthRequest{} + err := c.ShouldBind(&request) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage("ShouldBind"+err.Error())) + return + } + + ret, err := svc.Auth(&request) + c.Set(api.ReqResult, ret) + c.Set(api.ReqError, err) +} diff --git a/service/auth/server/http/http.go b/service/auth/server/http/http.go new file mode 100644 index 0000000..eb84455 --- /dev/null +++ b/service/auth/server/http/http.go @@ -0,0 +1,69 @@ +package http + +import ( + "github.com/gin-gonic/gin" + "github.com/inconshreveable/log15" + + swaggerFiles "github.com/swaggo/files" + ginSwagger "github.com/swaggo/gin-swagger" + "gitlab.33.cn/chat/dtalk/pkg/api" + _ "gitlab.33.cn/chat/dtalk/service/auth/docs" + "gitlab.33.cn/chat/dtalk/service/auth/service" + "net/http" +) + +var ( + svc *service.Service + log = log15.New("module", "auth/http") +) + +func Init(s *service.Service) *http.Server { + addr := s.Config().Server.Addr + engine := Default() + InitService(s) + SetupEngine(engine) + + srv := &http.Server{ + Addr: addr, + Handler: engine, + } + go func() { + // service connections + if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Error("engineInner.Start() error(%v)", err) + panic(err) + } + }() + return srv +} + +// Default returns an Engine instance with the Logger and Recovery middleware already attached. +func Default() *gin.Engine { + router := gin.New() + // LoggerWithFormatter middleware will write the logs to gin.DefaultWriter + // By default gin.DefaultWriter = os.Stdout + router.Use(gin.LoggerWithFormatter(api.Chat33GinLogFormatter)) + router.Use(gin.Recovery()) + return router +} + +func InitService(s *service.Service) { + svc = s +} + +// SetupEngine +// @title auth服务接口 +// @version 0.0.1 +// @host 127.0.0.1:18103 +func SetupEngine(e *gin.Engine) *gin.Engine { + + auth := e.Group("/auth", api.RespMiddleWare()) + { + auth.POST("/sign-in", SignIn) + auth.POST("/auth", Auth) + } + + // swagger 文档接口 + e.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) + return e +} diff --git a/service/auth/server/http/sign_in.go b/service/auth/server/http/sign_in.go new file mode 100644 index 0000000..e2c4a61 --- /dev/null +++ b/service/auth/server/http/sign_in.go @@ -0,0 +1,35 @@ +package http + +import ( + "github.com/gin-gonic/gin" + "gitlab.33.cn/chat/dtalk/pkg/api" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/service/auth/model" +) + +// SignIn +// @Summary 注册一个新的 AppId +// @Author wyt@33.cn +// @Tags backend +// @Accept multipart/form-data +// @Param file formData file true "file" +// @Param data formData model.SignInRequest true "body" +// @Success 200 {object} model.GeneralResponse{data=model.SignInResponse} +// @Router /auth/sign-in [post] +func SignIn(c *gin.Context) { + request := model.SignInRequest{} + err := c.ShouldBind(&request) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage("ShouldBindHeader"+err.Error())) + return + } + request.ConfigFile, err = c.FormFile("configFile") + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage("FormFile"+err.Error())) + return + } + + ret, err := svc.SignIn(&request) + c.Set(api.ReqResult, ret) + c.Set(api.ReqError, err) +} diff --git a/service/auth/service/auth.go b/service/auth/service/auth.go new file mode 100644 index 0000000..de8162d --- /dev/null +++ b/service/auth/service/auth.go @@ -0,0 +1,231 @@ +package service + +import ( + "crypto/sha256" + "encoding/hex" + "encoding/json" + publicUtil "gitlab.33.cn/chat/dtalk/pkg/util" + "gitlab.33.cn/chat/dtalk/service/auth/model" + "gopkg.in/yaml.v2" + "io/ioutil" + "net/http" + "strings" + "time" +) + +func (s *Service) Auth(request *model.AuthRequest) (res *model.AuthResponse, err error) { + defer func() { + if err != nil { + s.log.Error("Auth", "err", err, "req", request) + } else { + s.log.Info("Auth", "req", request) + } + }() + + //解析请求中的token + digest, createTime, token, err := s.ParseTokenInRequest(&request.Token) + + if err != nil { + return &model.AuthResponse{}, model.ErrInvalidToken + } + + if !time.Now().Before(time.Unix(0, createTime).Add(s.cfg.Key.KeyExpireDuration)) { + return &model.AuthResponse{}, model.ErrInvalidRequest + } + + authInfo := model.AuthInfo{ + AppId: request.AppId, + Token: token, + Digest: digest, + CreateTime: createTime, + } + + //获取key + key, err := s.dao.GetKey(authInfo.AppId) + if err != nil { + return &model.AuthResponse{}, model.ErrQueryKey + } + if key == "" && err == nil { + return &model.AuthResponse{}, model.ErrKeyIsNonexistent + } + + //检验digest + result, err := s.checkDigest(&authInfo, key) + if err != nil { + return &model.AuthResponse{}, model.ErrCheckDigest + } + if result == false && err == nil { + return &model.AuthResponse{}, model.ErrInvalidRequest + } + + //加载配置 + configString, err := s.dao.LoadConfig(authInfo.AppId) + if err != nil { + return &model.AuthResponse{}, model.ErrLoadConfig + } + if configString == "" && err == nil { + return &model.AuthResponse{}, model.ErrConfigurationIsNonexistent + } + + //分析配置 + appConfig, err := s.ParseConfigString(configString) + if err != nil { + return &model.AuthResponse{}, model.ErrParseConfig + } + + //发送请求 + var bodyOfResponse []byte + bodyOfResponse, err = s.issueHTTPCommunication(appConfig.BasicConfig.Method, appConfig.BasicConfig.Url, appConfig.Request.TokenName, authInfo.Token) + if err != nil { + return &model.AuthResponse{}, model.ErrHTTPCommunication + } + dataOfResponse := make(map[string]interface{}) + err = json.Unmarshal(bodyOfResponse, &dataOfResponse) + if err != nil { + return &model.AuthResponse{}, model.ErrUnmarshalBodyOfResponse + } + + //比较返回结果 + successValue := appConfig.Response.SuccessResult + result, err = s.CompareValues(successValue, dataOfResponse) + if err != nil { + return &model.AuthResponse{}, err + } + if result != true { + return &model.AuthResponse{}, model.ErrAuthFiled + } + + //获取uid + uidName := appConfig.Response.UidName + uid, found := s.FindUid(uidName, dataOfResponse) + if found { + return &model.AuthResponse{ + Uid: uid, + }, nil + } + + return &model.AuthResponse{}, model.ErrFindUid +} + +func (s *Service) ParseConfigString(configString string) (*model.AppConfig, error) { + appConfig := model.AppConfig{} + err := yaml.Unmarshal([]byte(configString), &appConfig) + if err != nil { + return nil, err + } + + return &appConfig, nil +} + +func (s *Service) issueHTTPCommunication(method string, url string, tokenName string, token string) ([]byte, error) { + request, err := http.NewRequest(method, url, nil) + if err != nil { + return nil, err + } + + request.Header.Set(tokenName, token) + + response, err := s.httpClient.Do(request) + if err != nil { + return nil, err + } + defer response.Body.Close() + + bodyOfResponse, err := ioutil.ReadAll(response.Body) + if err != nil { + return nil, err + } + + return bodyOfResponse, nil +} + +func (s *Service) CompareValues(srcValue interface{}, destValue interface{}) (bool, error) { + result := false + //判断srcValue是否是映射 + if subValueOfSrcValue, ok := srcValue.(map[interface{}]interface{}); ok { + //判断destValue与srcValue格式是否一致 + subValueOfDestValue, ok := destValue.(map[string]interface{}) + if !ok { + result = false + return result, model.ErrWrongDataFormat + } + //遍历映射,对值进行比较 + for name, grandValueOfSrcValue := range subValueOfSrcValue { + stringOfName := publicUtil.ToString(name) + //判断是否存在该键值对 + grandValueOfDestValue, exist := subValueOfDestValue[stringOfName] + if !exist { + result = false + return result, model.ErrFieldIsNonexistentInCompareValues + } + //对值进行递归比较 + result, err := s.CompareValues(grandValueOfSrcValue, grandValueOfDestValue) + if result == false { + return result, err + } + } + result = true + return result, nil + } + //将值转换成字符串比较 + srcValueString := publicUtil.ToString(srcValue) + destValueString := publicUtil.ToString(destValue) + + if srcValueString == destValueString { + result = true + } + + return result, nil +} + +func (s *Service) FindUid(uidName string, result interface{}) (string, bool) { + uid := "" + found := false + //递归查找uid + if subResult, ok := result.(map[string]interface{}); ok { + for name, value := range subResult { + if grandResult, ok := value.(map[string]interface{}); ok { + uid, found = s.FindUid(uidName, grandResult) + if found { + return uid, found + } + } else if name == uidName { + uid = publicUtil.ToString(value) + found = true + return uid, found + } + } + } + + return uid, found +} + +func (s *Service) checkDigest(authInfo *model.AuthInfo, key string) (bool, error) { + str := authInfo.AppId + authInfo.Token + publicUtil.ToString(authInfo.CreateTime) + key + hash := sha256.New() + _, err := hash.Write([]byte(str)) + if err != nil { + return false, err + } + + src := hash.Sum(nil) + //转化为十六进制字符串 + digest := hex.EncodeToString(src) + + if digest != authInfo.Digest { + return false, nil + } + + return true, nil +} + +func (s *Service) ParseTokenInRequest(tokenInRequest *string) (digest string, createTime int64, token string, err error) { + stringArray := strings.SplitN(*tokenInRequest, "$", 3) + if len(stringArray) < 3 { + return "", 0, "", model.ErrInvalidToken + } + digest = stringArray[0] + createTime = publicUtil.ToInt64(stringArray[1]) + token = stringArray[2] + return digest, createTime, token, nil +} diff --git a/service/auth/service/service.go b/service/auth/service/service.go new file mode 100644 index 0000000..3660976 --- /dev/null +++ b/service/auth/service/service.go @@ -0,0 +1,36 @@ +package service + +import ( + "github.com/inconshreveable/log15" + "gitlab.33.cn/chat/dtalk/service/auth/config" + "gitlab.33.cn/chat/dtalk/service/auth/dao" + "gitlab.33.cn/chat/dtalk/service/auth/model" + "net/http" +) + +type Service struct { + log log15.Logger + cfg *config.Config + dao *dao.Dao + httpClient *http.Client +} + +func New(cfg *config.Config) *Service { + s := &Service{ + log: log15.New("module", "auth/svc"), + cfg: cfg, + dao: dao.New(cfg), + httpClient: &http.Client{ + Timeout: model.Timeout, + }, + } + return s +} + +func (s *Service) Ping() error { + return nil +} + +func (s Service) Config() *config.Config { + return s.cfg +} diff --git a/service/auth/service/sign_in.go b/service/auth/service/sign_in.go new file mode 100644 index 0000000..df84bae --- /dev/null +++ b/service/auth/service/sign_in.go @@ -0,0 +1,82 @@ +package service + +import ( + "crypto/sha256" + "encoding/hex" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/service/auth/model" + "io/ioutil" + "math/rand" + "time" +) + +func (s *Service) SignIn(request *model.SignInRequest) (res *model.SignInResponse, err error) { + defer func() { + if err != nil { + s.log.Error("SignIn", "err", err, "req", request) + } else { + s.log.Info("SignIn", "req", request) + } + }() + + signInInfo := model.SignInInfo{ + AppId: request.AppId, + ConfigFile: request.ConfigFile, + CreateTime: time.Now().UnixNano() / 1e6, + UpdateTime: time.Now().UnixNano() / 1e6, + } + + //读取配置文件内容 + file, err := signInInfo.ConfigFile.Open() + if err != nil { + return nil, err + } + defer file.Close() + buf, err := ioutil.ReadAll(file) + if err != nil { + return nil, err + } + configString := string(buf) + //生成key + key, err := s.createKey(signInInfo.AppId) + if err != nil { + return nil, err + } + + num, err := s.dao.SignIn(signInInfo.AppId, &configString, key, signInInfo.CreateTime, signInInfo.UpdateTime) + if err != nil { + return nil, err + } + + if num == 0 { + return nil, xerror.NewError(xerror.ParamsError).SetExtMessage("AppId已存在") + } + + return &model.SignInResponse{ + AppId: signInInfo.AppId, + Key: key, + CreateTime: signInInfo.CreateTime, + UpdateTime: signInInfo.UpdateTime, + }, nil +} + +func (s *Service) createKey(appId string) (string, error) { + rand.Seed(time.Now().Unix()) + salt := "" + hash := sha256.New() + + for i := 0; i < model.Length; i++ { + salt = salt + string(model.Chars[rand.Intn(len(model.Chars))]) + } + finalString := appId + salt + _, err := hash.Write([]byte(finalString)) + if err != nil { + return "", err + } + + srcKeyBytes := hash.Sum(nil) + //转化为十六进制字符串 + dstKey := hex.EncodeToString(srcKeyBytes) + + return dstKey, nil +} diff --git a/service/auth/version.go b/service/auth/version.go new file mode 100644 index 0000000..4c25552 --- /dev/null +++ b/service/auth/version.go @@ -0,0 +1,16 @@ +package version + +var ( + // The full version string + Version = "0.0.1" + + // GitCommit is set with --ldflags "-X main.gitCommit=$(git rev-parse --short=8 HEAD)" + GitCommit string +) + +func GetVersion() string { + if GitCommit != "" { + return Version + "-" + GitCommit + } + return Version +} diff --git a/service/backend/CHANGELOG.md b/service/backend/CHANGELOG.md new file mode 100644 index 0000000..e814885 --- /dev/null +++ b/service/backend/CHANGELOG.md @@ -0,0 +1,56 @@ +版本号`major.minor.patch`具体规则如下: +- major:主版本号,如有重大版本重构则该字段递增,通常各主版本间接口不兼容。 +- minor:次版本号,各次版本号间接口保持兼容,如有接口新增或优化则该字段递增。 +- patch:补丁号,如有功能改善或缺陷修复则该字段递增。 + +## version 0.2.3 + +**配置文件更新** +```toml +CdkMod = false # true 表示开启 cdk 模块, false 表示关闭 +``` +**Feature** +- cdk 模块设计开关 v0.2.3 2021_11_09 + +## version 0.2.0-0.2.2 + +**配置文件更新** +```toml +Env = "release" + +[IdGenRPCClient] +RegAddrs = "127.0.0.1:2379" +Schema = "dtalk" +SrvName = "generator" +Dial = "1s" +Timeout = "1s" +``` + +**数据库更新** +新增两张表 +- dtalk_cdk_info +- dtalk_cdk_list + +**Feature** +- 新增 cdk 相关接口 v0.2.0 2021_10_22 +- 新增接口参数约束, 每种优惠券兑换数量上限 v0.2.1 2021_10_31 +- 异步处理订单, 后端查询交易是否成功 v0.2.2 2021_11_01 + +## version 0.1.0 + +- platform 从配置文件中获得 v0.1.0 2021_09_18 + +## version 0.0.10 + +init backend + + +## example x.x.x @yy.mm.dd + +**Feature** + +**Bug Fixes** + +**Improvement** + +**Breaking Change** diff --git a/service/backend/Makefile b/service/backend/Makefile new file mode 100644 index 0000000..a1f7e5b --- /dev/null +++ b/service/backend/Makefile @@ -0,0 +1,50 @@ +# golang1.9 or latest +# 1. make help +# 2. make dep +# 3. make build +# ... + +VERSION := $(shell echo $(shell cat cmd/main.go | grep "projectVersion =" | cut -d '=' -f2)) +APP_NAME := backend +BUILD_DIR := build +APP := ${BUILD_DIR}/${APP_NAME}_v${VERSION} +PKG_NAME := ${APP_NAME}_v${VERSION} +PKG := ${PKG_NAME}.tar.gz + +main_path = "main" +go_version = $(shell go version | awk '{ print $3 }') +build_time = $(shell date "+%Y-%m-%d %H:%M:%S %Z") +git_commit = $(shell git rev-parse --short=10 HEAD) +flags := -ldflags "-X '${main_path}.goVersion=${go_version}' \ +-X '${main_path}.buildTime=${build_time}' \ +-X '${main_path}.gitCommit=${git_commit}' \ +-X 'google.golang.org/protobuf/reflect/protoregistry.conflictPolicy=warn'" + + +.PHONY: clean build pkg + +clean: ## Remove previous build + @rm -rf ${BUILD_DIR} + @go clean + +swag: + swag init -g server/http/http.go + +build: swag#checkgofmt ## Build the binary file + GOOS=linux GOARCH=amd64 GO111MODULE=on GOPROXY=https://goproxy.cn,direct GOSUMDB="sum.golang.google.cn" go build -v $(flags) -o $(APP) cmd/main.go + +pkg: build ## Package + mkdir -p ${PKG_NAME}/bin + mkdir -p ${PKG_NAME}/etc + cp ${APP} ${PKG_NAME}/bin/ + cp etc/* ${PKG_NAME}/etc/ + tar zvcf ${PKG} ${PKG_NAME} + rm -rf ${PKG_NAME} + +REMOTE_BIN_PATH := /opt/dtalk/srv/app/bin +upload: build + rsync -r $(APP) 107:$(REMOTE_BIN_PATH) + ssh 107 "cd $(REMOTE_BIN_PATH) && chmod 777 $(PKG_NAME) && ln -sf $(PKG_NAME) $(APP_NAME) && supervisorctl restart $(APP_NAME)" + +run: swag + go run cmd/main.go -conf config/backend.toml \ No newline at end of file diff --git a/service/backend/api/api.pb.go b/service/backend/api/api.pb.go new file mode 100644 index 0000000..96719b6 --- /dev/null +++ b/service/backend/api/api.pb.go @@ -0,0 +1,825 @@ +// protoc -I=. -I=$GOPATH/src --go_out=plugins=grpc:. *.proto + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc v3.19.1 +// source: api.proto + +package backend + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type CreateRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Platform string `protobuf:"bytes,1,opt,name=platform,proto3" json:"platform,omitempty"` + Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` + Force int32 `protobuf:"varint,3,opt,name=force,proto3" json:"force,omitempty"` + Url string `protobuf:"bytes,4,opt,name=url,proto3" json:"url,omitempty"` + VersionCode string `protobuf:"bytes,5,opt,name=version_code,json=versionCode,proto3" json:"version_code,omitempty"` + VersionName string `protobuf:"bytes,6,opt,name=version_name,json=versionName,proto3" json:"version_name,omitempty"` + DeviceType string `protobuf:"bytes,7,opt,name=device_type,json=deviceType,proto3" json:"device_type,omitempty"` +} + +func (x *CreateRequest) Reset() { + *x = CreateRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateRequest) ProtoMessage() {} + +func (x *CreateRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateRequest.ProtoReflect.Descriptor instead. +func (*CreateRequest) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{0} +} + +func (x *CreateRequest) GetPlatform() string { + if x != nil { + return x.Platform + } + return "" +} + +func (x *CreateRequest) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *CreateRequest) GetForce() int32 { + if x != nil { + return x.Force + } + return 0 +} + +func (x *CreateRequest) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +func (x *CreateRequest) GetVersionCode() string { + if x != nil { + return x.VersionCode + } + return "" +} + +func (x *CreateRequest) GetVersionName() string { + if x != nil { + return x.VersionName + } + return "" +} + +func (x *CreateRequest) GetDeviceType() string { + if x != nil { + return x.DeviceType + } + return "" +} + +type UpdateRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Description string `protobuf:"bytes,1,opt,name=description,proto3" json:"description,omitempty"` + Force int32 `protobuf:"varint,2,opt,name=force,proto3" json:"force,omitempty"` + Url string `protobuf:"bytes,3,opt,name=url,proto3" json:"url,omitempty"` + VersionCode string `protobuf:"bytes,4,opt,name=version_code,json=versionCode,proto3" json:"version_code,omitempty"` + VersionName string `protobuf:"bytes,5,opt,name=version_name,json=versionName,proto3" json:"version_name,omitempty"` + Id int64 `protobuf:"varint,6,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *UpdateRequest) Reset() { + *x = UpdateRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateRequest) ProtoMessage() {} + +func (x *UpdateRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateRequest.ProtoReflect.Descriptor instead. +func (*UpdateRequest) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{1} +} + +func (x *UpdateRequest) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *UpdateRequest) GetForce() int32 { + if x != nil { + return x.Force + } + return 0 +} + +func (x *UpdateRequest) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +func (x *UpdateRequest) GetVersionCode() string { + if x != nil { + return x.VersionCode + } + return "" +} + +func (x *UpdateRequest) GetVersionName() string { + if x != nil { + return x.VersionName + } + return "" +} + +func (x *UpdateRequest) GetId() int64 { + if x != nil { + return x.Id + } + return 0 +} + +type ChangeStatusRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + Status int32 `protobuf:"varint,2,opt,name=status,proto3" json:"status,omitempty"` + DeviceType string `protobuf:"bytes,3,opt,name=device_type,json=deviceType,proto3" json:"device_type,omitempty"` +} + +func (x *ChangeStatusRequest) Reset() { + *x = ChangeStatusRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ChangeStatusRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ChangeStatusRequest) ProtoMessage() {} + +func (x *ChangeStatusRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ChangeStatusRequest.ProtoReflect.Descriptor instead. +func (*ChangeStatusRequest) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{2} +} + +func (x *ChangeStatusRequest) GetId() int64 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *ChangeStatusRequest) GetStatus() int32 { + if x != nil { + return x.Status + } + return 0 +} + +func (x *ChangeStatusRequest) GetDeviceType() string { + if x != nil { + return x.DeviceType + } + return "" +} + +type CheckAndUpdateRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + DeviceType string `protobuf:"bytes,2,opt,name=device_type,json=deviceType,proto3" json:"device_type,omitempty"` +} + +func (x *CheckAndUpdateRequest) Reset() { + *x = CheckAndUpdateRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CheckAndUpdateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CheckAndUpdateRequest) ProtoMessage() {} + +func (x *CheckAndUpdateRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CheckAndUpdateRequest.ProtoReflect.Descriptor instead. +func (*CheckAndUpdateRequest) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{3} +} + +func (x *CheckAndUpdateRequest) GetId() int64 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *CheckAndUpdateRequest) GetDeviceType() string { + if x != nil { + return x.DeviceType + } + return "" +} + +type CreateReply struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *CreateReply) Reset() { + *x = CreateReply{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateReply) ProtoMessage() {} + +func (x *CreateReply) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateReply.ProtoReflect.Descriptor instead. +func (*CreateReply) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{4} +} + +func (x *CreateReply) GetId() int64 { + if x != nil { + return x.Id + } + return 0 +} + +type UpdateReply struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Num int64 `protobuf:"varint,1,opt,name=num,proto3" json:"num,omitempty"` +} + +func (x *UpdateReply) Reset() { + *x = UpdateReply{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateReply) ProtoMessage() {} + +func (x *UpdateReply) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateReply.ProtoReflect.Descriptor instead. +func (*UpdateReply) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{5} +} + +func (x *UpdateReply) GetNum() int64 { + if x != nil { + return x.Num + } + return 0 +} + +type ChangeStatusReply struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Num int64 `protobuf:"varint,1,opt,name=num,proto3" json:"num,omitempty"` +} + +func (x *ChangeStatusReply) Reset() { + *x = ChangeStatusReply{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ChangeStatusReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ChangeStatusReply) ProtoMessage() {} + +func (x *ChangeStatusReply) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ChangeStatusReply.ProtoReflect.Descriptor instead. +func (*ChangeStatusReply) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{6} +} + +func (x *ChangeStatusReply) GetNum() int64 { + if x != nil { + return x.Num + } + return 0 +} + +type CheckAndUpdateReply struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + Platform string `protobuf:"bytes,2,opt,name=platform,proto3" json:"platform,omitempty"` + Status int32 `protobuf:"varint,3,opt,name=status,proto3" json:"status,omitempty"` + DeviceType string `protobuf:"bytes,4,opt,name=device_type,json=deviceType,proto3" json:"device_type,omitempty"` + VersionCode string `protobuf:"bytes,5,opt,name=version_code,json=versionCode,proto3" json:"version_code,omitempty"` + Url string `protobuf:"bytes,6,opt,name=url,proto3" json:"url,omitempty"` + Force int32 `protobuf:"varint,7,opt,name=force,proto3" json:"force,omitempty"` + Description string `protobuf:"bytes,8,opt,name=description,proto3" json:"description,omitempty"` + OpeUser string `protobuf:"bytes,9,opt,name=ope_user,json=opeUser,proto3" json:"ope_user,omitempty"` + UpdateTime int64 `protobuf:"varint,10,opt,name=update_time,json=updateTime,proto3" json:"update_time,omitempty"` + CreateTime int64 `protobuf:"varint,11,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty"` +} + +func (x *CheckAndUpdateReply) Reset() { + *x = CheckAndUpdateReply{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CheckAndUpdateReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CheckAndUpdateReply) ProtoMessage() {} + +func (x *CheckAndUpdateReply) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CheckAndUpdateReply.ProtoReflect.Descriptor instead. +func (*CheckAndUpdateReply) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{7} +} + +func (x *CheckAndUpdateReply) GetId() int64 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *CheckAndUpdateReply) GetPlatform() string { + if x != nil { + return x.Platform + } + return "" +} + +func (x *CheckAndUpdateReply) GetStatus() int32 { + if x != nil { + return x.Status + } + return 0 +} + +func (x *CheckAndUpdateReply) GetDeviceType() string { + if x != nil { + return x.DeviceType + } + return "" +} + +func (x *CheckAndUpdateReply) GetVersionCode() string { + if x != nil { + return x.VersionCode + } + return "" +} + +func (x *CheckAndUpdateReply) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +func (x *CheckAndUpdateReply) GetForce() int32 { + if x != nil { + return x.Force + } + return 0 +} + +func (x *CheckAndUpdateReply) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *CheckAndUpdateReply) GetOpeUser() string { + if x != nil { + return x.OpeUser + } + return "" +} + +func (x *CheckAndUpdateReply) GetUpdateTime() int64 { + if x != nil { + return x.UpdateTime + } + return 0 +} + +func (x *CheckAndUpdateReply) GetCreateTime() int64 { + if x != nil { + return x.CreateTime + } + return 0 +} + +var File_api_proto protoreflect.FileDescriptor + +var file_api_proto_rawDesc = []byte{ + 0x0a, 0x09, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0d, 0x64, 0x74, 0x61, + 0x6c, 0x6b, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x22, 0xdc, 0x01, 0x0a, 0x0d, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, + 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, + 0x72, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, + 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, + 0x72, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, + 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x22, 0xaf, 0x01, 0x0a, 0x0d, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x0b, 0x64, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, + 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x66, 0x6f, + 0x72, 0x63, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x76, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, + 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x22, 0x5e, 0x0a, 0x13, 0x43, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, + 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x22, 0x48, 0x0a, 0x15, 0x43, + 0x68, 0x65, 0x63, 0x6b, 0x41, 0x6e, 0x64, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x02, 0x69, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x74, + 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x54, 0x79, 0x70, 0x65, 0x22, 0x1d, 0x0a, 0x0b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, + 0x65, 0x70, 0x6c, 0x79, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x02, 0x69, 0x64, 0x22, 0x1f, 0x0a, 0x0b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, + 0x70, 0x6c, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6e, 0x75, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x03, 0x6e, 0x75, 0x6d, 0x22, 0x25, 0x0a, 0x11, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6e, 0x75, + 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x6e, 0x75, 0x6d, 0x22, 0xc4, 0x02, 0x0a, + 0x13, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x41, 0x6e, 0x64, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, + 0x65, 0x70, 0x6c, 0x79, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x02, 0x69, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, + 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0b, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x10, 0x0a, 0x03, + 0x75, 0x72, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x14, + 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x66, + 0x6f, 0x72, 0x63, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x70, 0x65, 0x5f, 0x75, 0x73, + 0x65, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x70, 0x65, 0x55, 0x73, 0x65, + 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, + 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, + 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, + 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, + 0x69, 0x6d, 0x65, 0x32, 0x56, 0x0a, 0x09, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, + 0x12, 0x49, 0x0a, 0x0d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x12, 0x1c, 0x2e, 0x64, 0x74, 0x61, 0x6c, 0x6b, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, + 0x64, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1a, 0x2e, 0x64, 0x74, 0x61, 0x6c, 0x6b, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x42, 0x29, 0x5a, 0x27, 0x67, + 0x69, 0x74, 0x6c, 0x61, 0x62, 0x2e, 0x33, 0x33, 0x2e, 0x63, 0x6e, 0x2f, 0x63, 0x68, 0x61, 0x74, + 0x2f, 0x64, 0x74, 0x61, 0x6c, 0x6b, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x62, + 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_api_proto_rawDescOnce sync.Once + file_api_proto_rawDescData = file_api_proto_rawDesc +) + +func file_api_proto_rawDescGZIP() []byte { + file_api_proto_rawDescOnce.Do(func() { + file_api_proto_rawDescData = protoimpl.X.CompressGZIP(file_api_proto_rawDescData) + }) + return file_api_proto_rawDescData +} + +var file_api_proto_msgTypes = make([]protoimpl.MessageInfo, 8) +var file_api_proto_goTypes = []interface{}{ + (*CreateRequest)(nil), // 0: dtalk.backend.CreateRequest + (*UpdateRequest)(nil), // 1: dtalk.backend.UpdateRequest + (*ChangeStatusRequest)(nil), // 2: dtalk.backend.ChangeStatusRequest + (*CheckAndUpdateRequest)(nil), // 3: dtalk.backend.CheckAndUpdateRequest + (*CreateReply)(nil), // 4: dtalk.backend.CreateReply + (*UpdateReply)(nil), // 5: dtalk.backend.UpdateReply + (*ChangeStatusReply)(nil), // 6: dtalk.backend.ChangeStatusReply + (*CheckAndUpdateReply)(nil), // 7: dtalk.backend.CheckAndUpdateReply +} +var file_api_proto_depIdxs = []int32{ + 0, // 0: dtalk.backend.Generator.CreateVersion:input_type -> dtalk.backend.CreateRequest + 4, // 1: dtalk.backend.Generator.CreateVersion:output_type -> dtalk.backend.CreateReply + 1, // [1:2] is the sub-list for method output_type + 0, // [0:1] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_api_proto_init() } +func file_api_proto_init() { + if File_api_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_api_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ChangeStatusRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CheckAndUpdateRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateReply); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateReply); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ChangeStatusReply); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CheckAndUpdateReply); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_api_proto_rawDesc, + NumEnums: 0, + NumMessages: 8, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_api_proto_goTypes, + DependencyIndexes: file_api_proto_depIdxs, + MessageInfos: file_api_proto_msgTypes, + }.Build() + File_api_proto = out.File + file_api_proto_rawDesc = nil + file_api_proto_goTypes = nil + file_api_proto_depIdxs = nil +} diff --git a/service/backend/api/api.proto b/service/backend/api/api.proto new file mode 100644 index 0000000..546240a --- /dev/null +++ b/service/backend/api/api.proto @@ -0,0 +1,67 @@ +// protoc -I=. -I=$GOPATH/src --go_out=plugins=grpc:. *.proto +syntax = "proto3"; + +package dtalk.backend; +option go_package = "gitlab.33.cn/chat/dtalk/service/backend"; + +message CreateRequest{ + string platform=1; + string description=2; + int32 force=3; + string url=4; + string version_code=5; + string version_name=6; + string device_type=7; +} + +message UpdateRequest{ + string description=1; + int32 force=2; + string url=3; + string version_code=4; + string version_name=5; + int64 id=6; +} + +message ChangeStatusRequest{ + int64 id=1; + int32 status=2; + string device_type=3; +} + +message CheckAndUpdateRequest{ + int64 id=1; + string device_type=2; +} + +message CreateReply{ + int64 id=1; + +} + +message UpdateReply{ + int64 num=1; +} + +message ChangeStatusReply{ + int64 num=1; +} + +message CheckAndUpdateReply{ + int64 id=1; + string platform=2; + int32 status=3; + string device_type=4; + string version_code=5; + string url=6; + int32 force=7; + string description=8; + string ope_user=9; + int64 update_time=10; + int64 create_time=11; +} + +service Generator { + rpc CreateVersion(CreateRequest) returns (CreateReply); +} + diff --git a/service/backend/api/api_grpc.pb.go b/service/backend/api/api_grpc.pb.go new file mode 100644 index 0000000..afce60b --- /dev/null +++ b/service/backend/api/api_grpc.pb.go @@ -0,0 +1,101 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. + +package backend + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// GeneratorClient is the client API for Generator service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type GeneratorClient interface { + CreateVersion(ctx context.Context, in *CreateRequest, opts ...grpc.CallOption) (*CreateReply, error) +} + +type generatorClient struct { + cc grpc.ClientConnInterface +} + +func NewGeneratorClient(cc grpc.ClientConnInterface) GeneratorClient { + return &generatorClient{cc} +} + +func (c *generatorClient) CreateVersion(ctx context.Context, in *CreateRequest, opts ...grpc.CallOption) (*CreateReply, error) { + out := new(CreateReply) + err := c.cc.Invoke(ctx, "/dtalk.backend.Generator/CreateVersion", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// GeneratorServer is the server API for Generator service. +// All implementations must embed UnimplementedGeneratorServer +// for forward compatibility +type GeneratorServer interface { + CreateVersion(context.Context, *CreateRequest) (*CreateReply, error) + mustEmbedUnimplementedGeneratorServer() +} + +// UnimplementedGeneratorServer must be embedded to have forward compatible implementations. +type UnimplementedGeneratorServer struct { +} + +func (UnimplementedGeneratorServer) CreateVersion(context.Context, *CreateRequest) (*CreateReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateVersion not implemented") +} +func (UnimplementedGeneratorServer) mustEmbedUnimplementedGeneratorServer() {} + +// UnsafeGeneratorServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to GeneratorServer will +// result in compilation errors. +type UnsafeGeneratorServer interface { + mustEmbedUnimplementedGeneratorServer() +} + +func RegisterGeneratorServer(s grpc.ServiceRegistrar, srv GeneratorServer) { + s.RegisterService(&Generator_ServiceDesc, srv) +} + +func _Generator_CreateVersion_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GeneratorServer).CreateVersion(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.backend.Generator/CreateVersion", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GeneratorServer).CreateVersion(ctx, req.(*CreateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// Generator_ServiceDesc is the grpc.ServiceDesc for Generator service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Generator_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "dtalk.backend.Generator", + HandlerType: (*GeneratorServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "CreateVersion", + Handler: _Generator_CreateVersion_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "api.proto", +} diff --git a/service/backend/api/create.sh b/service/backend/api/create.sh new file mode 100644 index 0000000..0e5dbf9 --- /dev/null +++ b/service/backend/api/create.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +protoc -I. -I$GOPATH/src \ + --go_out=. --go_opt=paths=source_relative \ + --go-grpc_out=. --go-grpc_opt=paths=source_relative \ + *.proto \ No newline at end of file diff --git a/service/backend/build/backend_v0.2.3 b/service/backend/build/backend_v0.2.3 new file mode 100644 index 0000000..0ded8c5 Binary files /dev/null and b/service/backend/build/backend_v0.2.3 differ diff --git a/service/backend/cmd/main.go b/service/backend/cmd/main.go new file mode 100644 index 0000000..72b5ce3 --- /dev/null +++ b/service/backend/cmd/main.go @@ -0,0 +1,89 @@ +package main + +import ( + "context" + "flag" + "fmt" + "os" + "os/signal" + "syscall" + "time" + + "github.com/inconshreveable/log15" + "gitlab.33.cn/chat/dtalk/service/backend/config" + "gitlab.33.cn/chat/dtalk/service/backend/server/http" + "gitlab.33.cn/chat/dtalk/service/backend/service" +) + +const srvName = "backend" + +var ( + // projectVersion 项目版本 + projectVersion = "0.2.3" + // goVersion go版本 + goVersion = "" + // gitCommit git提交commit id + gitCommit = "" + // buildTime 编译时间 + buildTime = "" + + isShowVersion = flag.Bool("v", false, "show project version") +) + +// showVersion 显示项目版本信息 +func showVersion(isShow bool) { + if isShow { + fmt.Printf("Project: %s\n", srvName) + fmt.Printf(" Version: %s\n", projectVersion) + fmt.Printf(" Go Version: %s\n", goVersion) + fmt.Printf(" Git Commit: %s\n", gitCommit) + fmt.Printf(" Build Time: %s\n", buildTime) + os.Exit(0) + } +} + +var log = log15.New("cmd", srvName) + +// @Title 聊天单模块集成测试 +// @Version 0.1 +// @Description +// @SecurityDefinitions.ApiKey ApiKeyAuth +// @In header +// @Name Authorization +// @BasePath / +func main() { + flag.Parse() + showVersion(*isShowVersion) + + if err := config.Init(); err != nil { + panic(err) + } + log.Info("config info:", + "Server", *config.Conf.Server) + // service init + svc := service.New(config.Conf) + httpSrv := http.Init(svc) + + // init signal + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT) + for { + s := <-c + log.Info("service get a signal %s", s.String()) + switch s { + case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT: + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := httpSrv.Shutdown(ctx); err != nil { + log.Error("server shutdown:", "err", err) + } + time.Sleep(time.Second * 2) + log.Info(srvName + " server exit") + return + case syscall.SIGHUP: + // TODO reload + default: + return + } + } +} diff --git a/service/backend/config/backend.toml b/service/backend/config/backend.toml new file mode 100644 index 0000000..75dfa6e --- /dev/null +++ b/service/backend/config/backend.toml @@ -0,0 +1,36 @@ +Env = "debug" +Platform = "谈信公安版" +CdkMod = false # true 表示开启 cdk 模块, false 表示关闭 + +[server] +addr = "0.0.0.0:18102" + +[MySQL] +Host = "127.0.0.1" # changeme (mysql 服务地址) +Port = 3306 # changeme (mysql 服务端口) +User = "root" # changeme (mysql 账号) +Pwd = "123456" # changeme (mysql 密码) +Db = "dtalk" + +[Debug] +Flag = false # changeme (线上环境为 false, 测试环境可以为 true) + +[Release] +Key = "123321" +Issuer = "Bob" +TokenExpireDuration = 86400000000000 +UserName = "root" # changeme (后台账号) +Password = "root" # changeme (后台密码) + +[IdGenRPCClient] +RegAddrs = "127.0.0.1:2379" # changeme (generator 服务注册的 etcd 地址) +Schema = "dtalk" +SrvName = "generator" +Dial = "1s" +Timeout = "1s" + +# CdkMod = false 可不配置 +[chain33Client] +BlockChainAddr= "172.16.101.87:8902" # changeme (钱包查询服务的 rpc 端口) +Title="user.p.testproofv2." + diff --git a/service/backend/config/config.go b/service/backend/config/config.go new file mode 100644 index 0000000..557752b --- /dev/null +++ b/service/backend/config/config.go @@ -0,0 +1,116 @@ +package config + +import ( + "flag" + "github.com/BurntSushi/toml" + xtime "gitlab.33.cn/chat/dtalk/pkg/time" + "time" +) + +var ( + confPath string + + Conf *Config +) + +func init() { + flag.StringVar(&confPath, "conf", "backend.toml", "default config path.") +} + +// Init init config. +func Init() (err error) { + Conf = Default() + _, err = toml.DecodeFile(confPath, &Conf) + return +} + +func Default() *Config { + return &Config{ + Env: "debug", + Platform: "chat33pro", + Server: &HttpServer{ + Addr: "0.0.0.0:18202", + }, + MySQL: &MySQL{ + Host: "127.0.0.1", + Port: 3306, + User: "root", + Pwd: "123456", + Db: "dtalk", + }, + Debug: &Debug{ + Flag: false, + }, + Release: &Release{ + Key: "123321", + Issuer: "Bob", + TokenExpireDuration: 86400000000000, + UserName: "root", + Password: "root", + }, + IdGenRPCClient: &RPCClient{ + RegAddrs: "127.0.0.1:2379", + Schema: "dtalk", + SrvName: "generator", + Dial: xtime.Duration(time.Second), + Timeout: xtime.Duration(time.Second), + }, + CdkMaxNumber: 10, + Chain33Client: Chain33Client{ + BlockChainAddr: "", + Title: "", + }, + CdkMod: false, + } +} + +type Config struct { + Env string + Platform string + Server *HttpServer + MySQL *MySQL + Debug *Debug + Release *Release + IdGenRPCClient *RPCClient + CdkMaxNumber int64 + Chain33Client Chain33Client + CdkMod bool +} + +type HttpServer struct { + Addr string +} + +type MySQL struct { + Host string + Port int32 + User string + Pwd string + Db string +} + +type Debug struct { + Flag bool +} + +type Release struct { + Key string + Issuer string + TokenExpireDuration time.Duration + UserName string + Password string +} + +// RPCClient is RPC client config. +type RPCClient struct { + RegAddrs string // etcd addrs, seperate by ',' + Schema string + SrvName string // call + Dial xtime.Duration + Timeout xtime.Duration +} + +type Chain33Client struct { + BlockChainAddr string + Title string +} diff --git a/service/backend/dao/cdkdb.go b/service/backend/dao/cdkdb.go new file mode 100644 index 0000000..fd030a7 --- /dev/null +++ b/service/backend/dao/cdkdb.go @@ -0,0 +1,418 @@ +package dao + +import ( + "gitlab.33.cn/chat/dtalk/pkg/util" + "gitlab.33.cn/chat/dtalk/service/backend/model/db" + "gitlab.33.cn/utils/go-kit/convert" + "math" + "strings" + "time" +) + +const ( + _InsertCdkType = `INSERT INTO dtalk_cdk_info ( cdk_id, cdk_name, cdk_info, coin_name, exchange_rate, create_time, update_time, delete_time)VALUES( ?, ?, ?, ?, ?, ?, ?, ?)` + _GetCdkTypes = `SELECT * FROM dtalk_cdk_info WHERE delete_time = 0 AND coin_name LIKE ? ORDER BY create_time DESC LIMIT ?,?` + _DeleteCdkType = `UPDATE dtalk_cdk_info SET delete_time = ? WHERE cdk_id = ? AND delete_time = 0` + _InsertCdk = `INSERT INTO dtalk_cdk_list ( id, cdk_id, cdk_content, user_id, cdk_status, order_id, create_time, update_time, delete_time, exchange_time)VALUES( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` + _GetCdks = `SELECT * FROM dtalk_cdk_list WHERE delete_time = 0 AND cdk_id = ? AND cdk_content LIKE ? ORDER BY create_time DESC LIMIT ?,?` + _DeleteCdk = `UPDATE dtalk_cdk_list SET delete_time = ? WHERE id = ? AND delete_time = 0` + _UpdateCdkStatus = `UPDATE dtalk_cdk_list SET cdk_status = ?, update_time = ? WHERE id = ? AND delete_time = 0` + _UpdateCdkUserId = `UPDATE dtalk_cdk_list SET user_id = ?, update_time = ? WHERE id = ? AND delete_time = 0` + _UpdateCdkOrderId = `UPDATE dtalk_cdk_list SET order_id = ?, cdk_status = 1, update_time = ? WHERE id = ? AND delete_time = 0` + _GetCdkTypesCount = `SELECT COUNT(*) FROM dtalk_cdk_info WHERE delete_time = 0 AND coin_name LIKE ?` + _GetCdksCount = `SELECT COUNT(*) FROM dtalk_cdk_list WHERE delete_time = 0 AND cdk_id = ?` + _GetCdksWithUserId = `SELECT * FROM dtalk_cdk_list WHERE delete_time = 0 AND user_id = ? AND cdk_status >= ? ORDER BY create_time DESC LIMIT ?,?` + _GetCdksCountWithUserId = `SELECT COUNT(*) FROM dtalk_cdk_list WHERE delete_time = 0 AND user_id = ? AND cdk_status >= ?` + _GetCdkTypesWithCoinName = `SELECT * FROM dtalk_cdk_info WHERE delete_time = 0 AND coin_name = ?` + _GetUnusedCdksCount = `SELECT COUNT(*) FROM dtalk_cdk_list WHERE delete_time = 0 AND cdk_id = ? AND cdk_status = 0` + _GetFrozenCdksCount = `SELECT COUNT(*) FROM dtalk_cdk_list WHERE delete_time = 0 AND cdk_id = ? AND cdk_status = 1` + _GetUsedCdksCount = `SELECT COUNT(*) FROM dtalk_cdk_list WHERE delete_time = 0 AND cdk_id = ? AND cdk_status >= 2` + _GetCdkType = `SELECT * FROM dtalk_cdk_info WHERE delete_time = 0 AND cdk_id = ?` + _DeleteCdks = `UPDATE dtalk_cdk_list SET delete_time = ? WHERE delete_time = 0 AND id in ` + _DeleteCdkTypes = `UPDATE dtalk_cdk_info SET delete_time = ? WHERE delete_time = 0 AND cdk_id in ` + _DeleteCdksByCdkIds = `UPDATE dtalk_cdk_list SET delete_time = ? WHERE delete_time = 0 AND cdk_id in ` + _UpdateCdkType = `UPDATE dtalk_cdk_info SET cdk_name = ?, coin_name = ?, exchange_rate = ?, update_time = ? WHERE delete_time = 0 AND cdk_id = ?` + + _GetCdksByOrderId = `SELECT * FROM dtalk_cdk_list WHERE delete_time = 0 AND order_id = ?` + _UpdateCdksStatus = `UPDATE dtalk_cdk_list SET cdk_status = ?, update_time = ?, exchange_time = ? WHERE delete_time = 0 AND id in ` + _FrozenCdksStatus = `UPDATE dtalk_cdk_list SET cdk_status = ?, user_id = ?, order_id = ?, update_time = ? WHERE delete_time = 0 AND id in ` + _GetUnusedCdks = `SELECT * FROM dtalk_cdk_list WHERE cdk_id = ? AND cdk_status = ? AND delete_time = 0 ORDER BY create_time ASC LIMIT 0,?` + + _CheckCdkExist = `SELECT COUNT(*) FROM dtalk_cdk_list WHERE delete_time = 0 AND cdk_id = ? AND cdk_content = ?` + _GetFrozenCdks = `SElECT * FROM dtalk_cdk_list WHERE delete_time = 0 AND cdk_status = 1` + _CleanFrozenCdks = `UPDATE dtalk_cdk_list SET cdk_status = ?, update_time = ? WHERE delete_time = 0 AND order_id = ?` + _GetCdksCountByUserIdAndCdkId = `SELECT COUNT(*) FROM dtalk_cdk_list WHERE delete_time = 0 AND cdk_id = ? AND user_id = ? AND cdk_status >= 1` +) + +func (d *Dao) InsertCdkType(cdkType *db.CdkType) error { + _, _, err := d.conn.Exec(_InsertCdkType, cdkType.CdkId, cdkType.CdkName, cdkType.CdkInfo, cdkType.CoinName, cdkType.ExchangeRate, cdkType.CreateTime, cdkType.UpdateTime, cdkType.DeleteTime) + return err +} + +func (d *Dao) InsertCdk(cdk *db.Cdk) error { + _, _, err := d.conn.Exec(_InsertCdk, cdk.Id, cdk.CdkId, cdk.CdkContent, cdk.UserId, cdk.CdkStatus, cdk.OrderId, cdk.CreateTime, cdk.UpdateTime, cdk.DeleteTime, cdk.ExchangeTime) + return err +} + +func (d *Dao) DeleteCdkType(cdkId int64, deleteTime int64) error { + _, _, err := d.conn.Exec(_DeleteCdkType, deleteTime, cdkId) + return err +} + +func (d *Dao) DeleteCdk(id int64, deleteTime int64) error { + _, _, err := d.conn.Exec(_DeleteCdk, deleteTime, id) + return err +} + +func (d *Dao) DeleteCdks(ids []int64, deleteTime int64) error { + idStrs := make([]string, 0, len(ids)) + for _, id := range ids { + idStrs = append(idStrs, convert.ToString(id)) + } + idStr := "(" + strings.Join(idStrs, ",") + ")" + execSql := _DeleteCdks + idStr + _, _, err := d.conn.Exec(execSql, deleteTime) + + return err +} + +func (d *Dao) DeleteCdkTypes(cdkIds []int64) error { + idStrs := make([]string, 0, len(cdkIds)) + for _, id := range cdkIds { + idStrs = append(idStrs, convert.ToString(id)) + } + idStr := "(" + strings.Join(idStrs, ",") + ")" + execSql := _DeleteCdkTypes + idStr + _, _, err := d.conn.Exec(execSql, d.getNowTime()) + + return err +} + +func (d *Dao) DeleteCdksByCdkIds(cdkIds []int64) error { + idStrs := make([]string, 0, len(cdkIds)) + for _, id := range cdkIds { + idStrs = append(idStrs, convert.ToString(id)) + } + idStr := "(" + strings.Join(idStrs, ",") + ")" + execSql := _DeleteCdksByCdkIds + idStr + _, _, err := d.conn.Exec(execSql, d.getNowTime()) + + return err +} + +func (d *Dao) UpdateCdkType(cdkId int64, cdkName, coinName string, exchangeRate int64) error { + _, _, err := d.conn.Exec(_UpdateCdkType, cdkName, coinName, exchangeRate, d.getNowTime(), cdkId) + return err +} + +func (d *Dao) UpdateCdkStatus(id int64, cdkStatus int32) error { + _, _, err := d.conn.Exec(_UpdateCdkStatus, cdkStatus, d.getNowTime(), id) + return err +} + +func (d *Dao) UpdateCdkUserId(id int64, userId int64) error { + _, _, err := d.conn.Exec(_UpdateCdkUserId, userId, d.getNowTime(), id) + return err +} + +func (d *Dao) UpdateCdkOrderId(id int64, orderId int64) error { + _, _, err := d.conn.Exec(_UpdateCdkOrderId, orderId, d.getNowTime(), id) + return err +} + +func (d *Dao) GetCdkType(cdkId int64) (*db.CdkType, error) { + var records []map[string]string + records, err := d.conn.Query(_GetCdkType, cdkId) + if err != nil { + return nil, err + } + response, err := d.ToCdkTypes(records) + if err != nil { + return nil, err + } + + if len(response) == 0 { + return nil, nil + } + + return &response[0], nil +} + +func (d *Dao) GetCdkTypes(coinName string, page int64, size int64) ([]db.CdkType, int64, int64, error) { + var records []map[string]string + offset := page * size + if coinName == "" { + coinName = "%" + } + records, err := d.conn.Query(_GetCdkTypes, coinName, offset, size) + if err != nil { + return nil, 0, 0, err + } + response, err := d.ToCdkTypes(records) + if err != nil { + return nil, 0, 0, err + } + if coinName == "" { + coinName = "%" + } + recordsCount, err := d.conn.Query(_GetCdkTypesCount, coinName) + if err != nil { + return nil, 0, 0, err + } + totalElements := convert.ToInt64(recordsCount[0]["COUNT(*)"]) + totalPages := int64(math.Ceil(float64(totalElements) / float64(size))) + return response, totalElements, totalPages, nil +} + +func (d *Dao) GetCdks(cdkId int64, cdkContent string, page int64, size int64) ([]db.Cdk, int64, int64, error) { + var records []map[string]string + offset := page * size + cdkContent += "%" + records, err := d.conn.Query(_GetCdks, cdkId, cdkContent, offset, size) + if err != nil { + return nil, 0, 0, err + } + response, err := d.ToCdks(records) + if err != nil { + return nil, 0, 0, err + } + recordsCount, err := d.conn.Query(_GetCdksCount, cdkId) + if err != nil { + return nil, 0, 0, err + } + totalElements := convert.ToInt64(recordsCount[0]["COUNT(*)"]) + totalPages := int64(math.Ceil(float64(totalElements) / float64(size))) + return response, totalElements, totalPages, nil +} + +func (d *Dao) GetCdksWithUserId(userId string, page int64, size int64) ([]db.Cdk, int64, int64, error) { + var records []map[string]string + offset := page * size + records, err := d.conn.Query(_GetCdksWithUserId, userId, db.CdkFrozen, offset, size) + if err != nil { + return nil, 0, 0, err + } + response, err := d.ToCdks(records) + if err != nil { + return nil, 0, 0, err + } + recordsCount, err := d.conn.Query(_GetCdksCountWithUserId, userId, db.CdkFrozen) + if err != nil { + return nil, 0, 0, err + } + totalElements := convert.ToInt64(recordsCount[0]["COUNT(*)"]) + totalPages := int64(math.Ceil(float64(totalElements) / float64(size))) + return response, totalElements, totalPages, nil +} + +func (d *Dao) GetCdkTypesWithCoinName(coinName string) ([]db.CdkType, error) { + var records []map[string]string + records, err := d.conn.Query(_GetCdkTypesWithCoinName, coinName) + if err != nil { + return nil, err + } + response, err := d.ToCdkTypes(records) + if err != nil { + return nil, err + } + return response, nil +} + +func (d *Dao) GetCdksCount(cdkId int64) (int64, int64, int64, error) { + res, err := d.conn.Query(_GetUnusedCdksCount, cdkId) + if err != nil { + return 0, 0, 0, err + } + Unused := convert.ToInt64(res[0]["COUNT(*)"]) + + res, err = d.conn.Query(_GetFrozenCdksCount, cdkId) + if err != nil { + return 0, 0, 0, err + + } + Frozen := convert.ToInt64(res[0]["COUNT(*)"]) + + res, err = d.conn.Query(_GetUsedCdksCount, cdkId) + if err != nil { + return 0, 0, 0, err + + } + Used := util.ToInt64(res[0]["COUNT(*)"]) + + return Unused, Frozen, Used, nil + +} + +func (d *Dao) GetUnusedCdksCount(cdkId string) (int64, error) { + recordsCount, err := d.conn.Query(_GetUnusedCdksCount, cdkId) + if err != nil { + return 0, err + } + response := convert.ToInt64(recordsCount[0]["COUNT(*)"]) + return response, nil +} + +func (d *Dao) GetFrozenCdksCount(cdkId string) (int64, error) { + recordsCount, err := d.conn.Query(_GetFrozenCdksCount, cdkId) + if err != nil { + return 0, err + } + response := convert.ToInt64(recordsCount[0]["COUNT(*)"]) + return response, nil +} + +func (d *Dao) GetUsedCdksCount(cdkId string) (int64, error) { + recordsCount, err := d.conn.Query(_GetUsedCdksCount, cdkId) + if err != nil { + return 0, err + } + response := convert.ToInt64(recordsCount[0]["COUNT(*)"]) + return response, nil +} + +func (d *Dao) ToCdkTypes(records []map[string]string) ([]db.CdkType, error) { + cdkTypes := make([]db.CdkType, len(records), len(records)) + for i, record := range records { + cdkType := db.CdkType{ + CdkId: convert.ToInt64(record["cdk_id"]), + CdkName: record["cdk_name"], + CdkInfo: record["cdl_info"], + CoinName: record["coin_name"], + ExchangeRate: convert.ToInt64(record["exchange_rate"]), + TimeInfo: db.TimeInfo{ + CreateTime: convert.ToInt64(record["create_time"]), + UpdateTime: convert.ToInt64(record["update_time"]), + DeleteTime: convert.ToInt64(record["delete_time"]), + }, + } + + cdkTypes[i] = cdkType + } + return cdkTypes, nil +} + +func (d *Dao) ToCdks(records []map[string]string) ([]db.Cdk, error) { + cdks := make([]db.Cdk, len(records), len(records)) + for i, record := range records { + cdk := db.Cdk{ + Id: convert.ToInt64(record["id"]), + CdkId: convert.ToInt64(record["cdk_id"]), + CdkContent: record["cdk_content"], + UserId: record["user_id"], + CdkStatus: convert.ToInt32(record["cdk_status"]), + OrderId: convert.ToInt64(record["order_id"]), + TimeInfo: db.TimeInfo{ + CreateTime: convert.ToInt64(record["create_time"]), + UpdateTime: convert.ToInt64(record["update_time"]), + DeleteTime: convert.ToInt64(record["delete_time"]), + }, + ExchangeTime: convert.ToInt64(record["exchange_time"]), + } + + //fmt.Println(cdk) + + cdks[i] = cdk + } + return cdks, nil +} + +func (d *Dao) UpdateCdksStatus(ids []int64, status int64) error { + idStrs := make([]string, 0, len(ids)) + for _, id := range ids { + idStrs = append(idStrs, convert.ToString(id)) + } + idStr := "(" + strings.Join(idStrs, ",") + ")" + + execSql := _UpdateCdksStatus + idStr + var exchangeTime int64 + if status == db.CdkUsed || status == db.CdkExchange { + exchangeTime = d.getNowTime() + } + _, _, err := d.conn.Exec(execSql, status, d.getNowTime(), exchangeTime) + + return err +} + +func (d *Dao) GetCdksByOrderId(orderId int64) ([]db.Cdk, error) { + var records []map[string]string + records, err := d.conn.Query(_GetCdksByOrderId, orderId) + if err != nil { + return nil, err + } + response, err := d.ToCdks(records) + if err != nil { + return nil, err + } + + return response, nil +} + +func (d *Dao) GetUnusedCdks(cdkId int64, number int64) ([]db.Cdk, error) { + var records []map[string]string + records, err := d.conn.Query(_GetUnusedCdks, cdkId, db.CdkUnused, number) + if err != nil { + return nil, err + } + response, err := d.ToCdks(records) + if err != nil { + return nil, err + } + return response, nil +} + +func (d *Dao) FrozenCdksStatus(ids []int64, userId string, orderId int64) error { + idStrs := make([]string, 0, len(ids)) + for _, id := range ids { + idStrs = append(idStrs, convert.ToString(id)) + } + idStr := "(" + strings.Join(idStrs, ",") + ")" + + execSql := _FrozenCdksStatus + idStr + + _, _, err := d.conn.Exec(execSql, db.CdkFrozen, userId, orderId, d.getNowTime()) + return err +} + +func (d *Dao) getNowTime() int64 { + return time.Now().UnixNano() / 1e6 +} + +func (d *Dao) CheckCdkExist(cdkId int64, cdkContent string) (bool, error) { + recordsCount, err := d.conn.Query(_CheckCdkExist, cdkId, cdkContent) + if err != nil { + return false, err + } + response := convert.ToInt64(recordsCount[0]["COUNT(*)"]) + return response >= 1, nil +} + +func (d *Dao) GetFrozenCdks() ([]db.Cdk, error) { + var records []map[string]string + records, err := d.conn.Query(_GetFrozenCdks) + if err != nil { + return nil, err + } + response, err := d.ToCdks(records) + if err != nil { + return nil, err + } + return response, nil +} + +func (d *Dao) CleanFrozenCdks(orderId int64) error { + _, _, err := d.conn.Exec(_CleanFrozenCdks, db.CdkUnused, d.getNowTime(), orderId) + if err != nil { + return err + } + + return nil +} + +func (d *Dao) GetCdksCountByUserIdAndCdkId(cdkId int64, userId string) (int64, error) { + recordsCount, err := d.conn.Query(_GetCdksCountByUserIdAndCdkId, cdkId, userId) + if err != nil { + return 0, err + } + response := convert.ToInt64(recordsCount[0]["COUNT(*)"]) + return response, nil +} diff --git a/service/backend/dao/dao.go b/service/backend/dao/dao.go new file mode 100644 index 0000000..b1815f3 --- /dev/null +++ b/service/backend/dao/dao.go @@ -0,0 +1,31 @@ +package dao + +import ( + "fmt" + "github.com/inconshreveable/log15" + "gitlab.33.cn/chat/dtalk/pkg/mysql" + "gitlab.33.cn/chat/dtalk/service/backend/config" +) + +type Dao struct { + log log15.Logger + conn *mysql.MysqlConn +} + +func New(c *config.Config) *Dao { + d := &Dao{ + log: log15.New("module", "backend/dao"), + conn: newDB(c.MySQL), + } + return d +} + +func newDB(cfg *config.MySQL) *mysql.MysqlConn { + c, err := mysql.NewMysqlConn(cfg.Host, fmt.Sprintf("%v", cfg.Port), + cfg.User, cfg.Pwd, cfg.Db, "UTF8MB4") + if err != nil { + log15.Error("mysql init failed", "err", err) + panic(err) + } + return c +} diff --git a/service/backend/dao/versiondb.go b/service/backend/dao/versiondb.go new file mode 100644 index 0000000..bbd043f --- /dev/null +++ b/service/backend/dao/versiondb.go @@ -0,0 +1,159 @@ +package dao + +import ( + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/pkg/util" + "gitlab.33.cn/chat/dtalk/service/backend/model" + "math" +) + +const ( + _InsertVersionInfo = `INSERT INTO dtalk_ver_backend ( platform, state, device_type, version_code, version_name, download_url, force_update,size,md5, description, ope_user, update_time, create_time)VALUES( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ,?,?)` + _UpdateVersionInfo = `UPDATE dtalk_ver_backend SET version_name=?,version_code=?,download_url=?,size=?,md5=?,description=?,force_update=?,update_time=?,ope_user=? WHERE id=?` + _ChangeVersionStatus = `UPDATE dtalk_ver_backend SET state=1,update_time=?,ope_user=? WHERE id=?` + _QueryOtherReleaseVersionInfo = `SELECT * FROM dtalk_ver_backend WHERE id!=? AND device_type=? AND platform=? AND state=1` + _ChangeOtherReleaseVersionStatus = `UPDATE dtalk_ver_backend SET state=0,update_time=?,ope_user=? WHERE id=?` + _GetVersionList = `SELECT * FROM dtalk_ver_backend WHERE platform LIKE ? AND device_type LIKE ? ORDER BY create_time DESC LIMIT ?,?` + _GetVersionCount = `SELECT COUNT(*) FROM dtalk_ver_backend WHERE platform LIKE ? AND device_type LIKE ?` + _GetForceVersionCount = `SELECT COUNT(*) AS force_num FROM dtalk_ver_backend WHERE force_update = 1 AND version_code > ? AND version_code < ?` + _GetReleaseVersionInfo = `SELECT * FROM dtalk_ver_backend WHERE device_type=? AND platform=? AND state = 1 ORDER BY id DESC LIMIT 1` + _QueryVersionInfoById = `SELECT * FROM dtalk_ver_backend WHERE id=?` +) + +func (d *Dao) InsertVersion(form *model.VersionForm) (int64, int64, error) { + num, lastId, err := d.conn.Exec(_InsertVersionInfo, form.Platform, form.Status, form.DeviceType, form.VersionCode, form.VersionName, form.Url, form.Force, form.Size, form.Md5, form.Description.ToString(), form.OpeUser, form.UpdateTime, form.CreateTime) + return num, lastId, err +} + +func (d *Dao) UpdateVersion(form *model.VersionForm) (*model.VersionForm, error) { + _, _, err := d.conn.Exec(_UpdateVersionInfo, form.VersionName, form.VersionCode, form.Url, form.Size, form.Md5, form.Description.ToString(), form.Force, form.UpdateTime, form.OpeUser, form.Id) + if err != nil { + return nil, err + } + records, err := d.conn.Query(_QueryVersionInfoById, form.Id) + if err != nil { + return nil, err + } + if len(records) < 1 { + return nil, xerror.NewError(xerror.ParamsError).SetExtMessage("无此id的记录") + } + record := records[0] + version, err := model.ConvertVersionForm(&record) + if err != nil { + return nil, err + } + return version, nil +} + +func (d *Dao) ChangeVersionStatus(form *model.VersionForm) error { + tx, err := d.conn.NewTx() + if err != nil { + return err + } + _, _, err = tx.Exec(_ChangeVersionStatus, form.UpdateTime, form.OpeUser, form.Id) + if err != nil { + return err + } + + records, err := tx.Query(_QueryVersionInfoById, form.Id) + if err != nil { + return err + } + if len(records) == 0 { + return xerror.NewError(xerror.ParamsError).SetExtMessage("无此id的记录") + } + deviceType := records[0]["device_type"] + platform := records[0]["platform"] + + records, err = tx.Query(_QueryOtherReleaseVersionInfo, form.Id, deviceType, platform) + if err != nil { + return err + } + if len(records) == 0 { + err = tx.Commit() + if err != nil { + return err + } + return nil + } + + id := util.ToInt64(records[0]["id"]) + _, _, err = tx.Exec(_ChangeOtherReleaseVersionStatus, form.UpdateTime, form.OpeUser, id) + if err != nil { + return err + } + err = tx.Commit() + if err != nil { + return err + } + return nil +} + +func (d *Dao) GetVersionList(form *model.VersionForm, page int64, size int64) (*[]model.VersionForm, int64, int64, error) { + var records []map[string]string + offset := page * size + records, err := d.conn.Query(_GetVersionList, form.Platform, form.DeviceType, offset, size) + if err != nil { + return nil, 0, 0, err + } + response, err := d.ConvertVersionList(&records) + if err != nil { + return nil, 0, 0, err + } + countRecords, err := d.conn.Query(_GetVersionCount, form.Platform, form.DeviceType) + if err != nil { + return nil, 0, 0, err + } + totalElements := util.ToInt64(countRecords[0]["COUNT(*)"]) + totalPages := int64(math.Ceil(float64(totalElements) / float64(size))) + return response, totalElements, totalPages, nil +} + +func (d *Dao) CheckAndUpdateVersion(form *model.VersionForm) (*model.VersionForm, error) { + records, err := d.conn.Query(_GetReleaseVersionInfo, form.DeviceType, form.Platform) + if err != nil { + return nil, err + } + + if len(records) < 1 { + return &model.VersionForm{}, nil + } + record, err := model.ConvertVersionForm(&records[0]) + if err != nil { + return nil, err + } + if !record.Force { + //判断期间是否包含强制更新版本 + record.Force, _ = d.checkHaveForce(util.ToInt(form.VersionCode), util.ToInt(record.VersionCode)) + } + return record, nil +} + +func (d *Dao) ConvertVersionList(records *[]map[string]string) (*[]model.VersionForm, error) { + //var versionList []model.VersionForm + versionList := make([]model.VersionForm, len(*records), len(*records)) + for i, record := range *records { + version, err := model.ConvertVersionForm(&record) + if err != nil { + return nil, err + } + versionList[i] = *version + } + return &versionList, nil +} + +func (d *Dao) checkHaveForce(oldVer, newVer int) (bool, error) { + if oldVer >= newVer { + return false, nil + } + records, err := d.conn.Query(_GetForceVersionCount, oldVer, newVer) + if err != nil { + return false, err + } + + if len(records) < 1 { + return false, nil + } + num := util.ToInt64(records[0]["force_num"]) + return num > 0, nil +} diff --git a/service/backend/docs/docs.go b/service/backend/docs/docs.go new file mode 100644 index 0000000..9a7b984 --- /dev/null +++ b/service/backend/docs/docs.go @@ -0,0 +1,954 @@ +// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT +// This file was generated by swaggo/swag + +package docs + +import ( + "bytes" + "encoding/json" + "strings" + + "github.com/alecthomas/template" + "github.com/swaggo/swag" +) + +var doc = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{.Description}}", + "title": "{{.Title}}", + "contact": {}, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/app/cdk/create-cdk-order": { + "post": { + "tags": [ + "Cdk App" + ], + "summary": "创建兑换订单", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.CreateCdkOrderReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.CreateCdkOrderResp" + } + } + } + ] + } + } + } + } + }, + "/app/cdk/deal-cdk-order": { + "post": { + "tags": [ + "Cdk App" + ], + "summary": "处理兑换订单", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.DealCdkOrderReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.DealCdkOrderResp" + } + } + } + ] + } + } + } + } + }, + "/app/cdk/get-cdk-type-by-coin-name": { + "post": { + "tags": [ + "Cdk App" + ], + "summary": "查询一个票券对应的 cdkType", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.GetCdkTypeByCoinNameReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.GetCdkTypeByCoinNameResp" + } + } + } + ] + } + } + } + } + }, + "/app/cdk/get-cdks-by-user-id": { + "post": { + "tags": [ + "Cdk App" + ], + "summary": "分页获得一个人拥有的 cdks", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.GetCdksByUserIdReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.GetCdksByUserIdResp" + } + } + } + ] + } + } + } + } + }, + "/backend/cdk/create-cdk-type": { + "post": { + "tags": [ + "Cdk 后台" + ], + "summary": "创建 CdkType", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.CreateCdkTypeReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.CreateCdkTypeResp" + } + } + } + ] + } + } + } + } + }, + "/backend/cdk/create-cdks": { + "post": { + "tags": [ + "Cdk 后台" + ], + "summary": "创建 Cdks", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.CreateCdksReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.CreateCdksResp" + } + } + } + ] + } + } + } + } + }, + "/backend/cdk/delete-cdk-types": { + "post": { + "tags": [ + "Cdk 后台" + ], + "summary": "删除 cdkTypes", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.DeleteCdkTypesReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.DeleteCdkTypesResp" + } + } + } + ] + } + } + } + } + }, + "/backend/cdk/delete-cdks": { + "post": { + "tags": [ + "Cdk 后台" + ], + "summary": "删除 cdks", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.DeleteCdksReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.DeleteCdksResp" + } + } + } + ] + } + } + } + } + }, + "/backend/cdk/exchange-cdks": { + "post": { + "tags": [ + "Cdk 后台" + ], + "summary": "兑换 cdks", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.ExchangeCdksReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.ExchangeCdksResp" + } + } + } + ] + } + } + } + } + }, + "/backend/cdk/get-cdk-types": { + "post": { + "tags": [ + "Cdk 查询" + ], + "summary": "分页获得 cdkType", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.GetCdkTypesReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.GetCdkTypesResp" + } + } + } + ] + } + } + } + } + }, + "/backend/cdk/get-cdks": { + "post": { + "tags": [ + "Cdk 查询" + ], + "summary": "分页获得 cdks", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.GetCdksReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.GetCdksResp" + } + } + } + ] + } + } + } + } + }, + "/backend/cdk/update-cdk-type": { + "post": { + "tags": [ + "Cdk 后台" + ], + "summary": "更新 cdkType", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.UpdateCdkTypeReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.UpdateCdkTypeResp" + } + } + } + ] + } + } + } + } + } + }, + "definitions": { + "types.Cdk": { + "type": "object", + "properties": { + "cdkContent": { + "type": "string" + }, + "cdkId": { + "type": "string" + }, + "cdkName": { + "type": "string" + }, + "cdkStatus": { + "type": "integer" + }, + "createTime": { + "type": "string" + }, + "exchangeTime": { + "type": "string" + }, + "id": { + "type": "string" + }, + "orderId": { + "type": "string" + }, + "userId": { + "type": "string" + } + } + }, + "types.CdkType": { + "type": "object", + "properties": { + "cdkAvailable": { + "description": "未发放的cdk数量", + "type": "integer" + }, + "cdkFrozen": { + "description": "冻结状态中的cdk数量", + "type": "integer" + }, + "cdkId": { + "type": "string" + }, + "cdkInfo": { + "type": "string" + }, + "cdkName": { + "type": "string" + }, + "cdkUsed": { + "description": "已发放的cdk数量", + "type": "integer" + }, + "coinName": { + "type": "string" + }, + "exchangeRate": { + "type": "integer" + } + } + }, + "types.CreateCdkOrderReq": { + "type": "object", + "required": [ + "cdkId", + "number" + ], + "properties": { + "cdkId": { + "description": "cdk 种类编号", + "type": "string" + }, + "number": { + "description": "兑换数量", + "type": "integer" + } + } + }, + "types.CreateCdkOrderResp": { + "type": "object", + "properties": { + "orderId": { + "description": "订单编号", + "type": "string" + } + } + }, + "types.CreateCdkTypeReq": { + "type": "object", + "required": [ + "coinName", + "exchangeRate" + ], + "properties": { + "cdkInfo": { + "type": "string" + }, + "cdkName": { + "type": "string" + }, + "coinName": { + "type": "string" + }, + "exchangeRate": { + "type": "integer" + } + } + }, + "types.CreateCdkTypeResp": { + "type": "object", + "properties": { + "cdkId": { + "type": "string" + } + } + }, + "types.CreateCdksReq": { + "type": "object", + "required": [ + "cdkContents", + "cdkId" + ], + "properties": { + "cdkContents": { + "type": "array", + "items": { + "type": "string" + } + }, + "cdkId": { + "type": "string" + } + } + }, + "types.CreateCdksResp": { + "type": "object" + }, + "types.DealCdkOrderReq": { + "type": "object", + "required": [ + "orderId", + "transferHash" + ], + "properties": { + "orderId": { + "description": "订单编号", + "type": "string" + }, + "result": { + "description": "处理结果", + "type": "boolean" + }, + "transferHash": { + "description": "转账记录 hash", + "type": "string" + } + } + }, + "types.DealCdkOrderResp": { + "type": "object" + }, + "types.DeleteCdkTypesReq": { + "type": "object", + "required": [ + "cdkIds" + ], + "properties": { + "cdkIds": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "types.DeleteCdkTypesResp": { + "type": "object" + }, + "types.DeleteCdksReq": { + "type": "object", + "required": [ + "ids" + ], + "properties": { + "ids": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "types.DeleteCdksResp": { + "type": "object" + }, + "types.ExchangeCdksReq": { + "type": "object", + "required": [ + "ids" + ], + "properties": { + "ids": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "types.ExchangeCdksResp": { + "type": "object" + }, + "types.GeneralResponse": { + "type": "object", + "properties": { + "data": { + "type": "object" + }, + "message": { + "type": "integer" + }, + "result": { + "type": "integer" + } + } + }, + "types.GetCdkTypeByCoinNameReq": { + "type": "object", + "required": [ + "coinName" + ], + "properties": { + "coinName": { + "type": "string" + } + } + }, + "types.GetCdkTypeByCoinNameResp": { + "type": "object", + "properties": { + "cdkAvailable": { + "description": "未发放的cdk数量", + "type": "integer" + }, + "cdkFrozen": { + "description": "冻结状态中的cdk数量", + "type": "integer" + }, + "cdkId": { + "type": "string" + }, + "cdkInfo": { + "type": "string" + }, + "cdkName": { + "type": "string" + }, + "cdkUsed": { + "description": "已发放的cdk数量", + "type": "integer" + }, + "coinName": { + "type": "string" + }, + "exchangeRate": { + "type": "integer" + } + } + }, + "types.GetCdkTypesReq": { + "type": "object", + "properties": { + "coinName": { + "type": "string" + }, + "page": { + "description": "页数", + "type": "integer" + }, + "pageSize": { + "description": "每页数量", + "type": "integer" + } + } + }, + "types.GetCdkTypesResp": { + "type": "object", + "properties": { + "cdkTypes": { + "type": "array", + "items": { + "$ref": "#/definitions/types.CdkType" + } + }, + "totalElements": { + "type": "integer" + }, + "totalPages": { + "type": "integer" + } + } + }, + "types.GetCdksByUserIdReq": { + "type": "object", + "properties": { + "page": { + "description": "页数", + "type": "integer" + }, + "pageSize": { + "description": "每页数量", + "type": "integer" + } + } + }, + "types.GetCdksByUserIdResp": { + "type": "object", + "properties": { + "cdks": { + "type": "array", + "items": { + "$ref": "#/definitions/types.Cdk" + } + }, + "totalElements": { + "type": "integer" + }, + "totalPages": { + "type": "integer" + } + } + }, + "types.GetCdksReq": { + "type": "object", + "required": [ + "cdkId" + ], + "properties": { + "cdkContent": { + "type": "string" + }, + "cdkId": { + "type": "string" + }, + "page": { + "description": "页数", + "type": "integer" + }, + "pageSize": { + "description": "每页数量", + "type": "integer" + } + } + }, + "types.GetCdksResp": { + "type": "object", + "properties": { + "cdks": { + "type": "array", + "items": { + "$ref": "#/definitions/types.Cdk" + } + }, + "totalElements": { + "type": "integer" + }, + "totalPages": { + "type": "integer" + } + } + }, + "types.UpdateCdkTypeReq": { + "type": "object", + "required": [ + "cdkId", + "cdkName", + "coinName", + "exchangeRate" + ], + "properties": { + "cdkId": { + "type": "string" + }, + "cdkName": { + "type": "string" + }, + "coinName": { + "type": "string" + }, + "exchangeRate": { + "type": "integer" + } + } + }, + "types.UpdateCdkTypeResp": { + "type": "object" + } + } +}` + +type swaggerInfo struct { + Version string + Host string + BasePath string + Schemes []string + Title string + Description string +} + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = swaggerInfo{ + Version: "", + Host: "", + BasePath: "", + Schemes: []string{}, + Title: "", + Description: "", +} + +type s struct{} + +func (s *s) ReadDoc() string { + sInfo := SwaggerInfo + sInfo.Description = strings.Replace(sInfo.Description, "\n", "\\n", -1) + + t, err := template.New("swagger_info").Funcs(template.FuncMap{ + "marshal": func(v interface{}) string { + a, _ := json.Marshal(v) + return string(a) + }, + }).Parse(doc) + if err != nil { + return doc + } + + var tpl bytes.Buffer + if err := t.Execute(&tpl, sInfo); err != nil { + return doc + } + + return tpl.String() +} + +func init() { + swag.Register(swag.Name, &s{}) +} diff --git a/service/backend/docs/swagger.json b/service/backend/docs/swagger.json new file mode 100644 index 0000000..dc6b2f0 --- /dev/null +++ b/service/backend/docs/swagger.json @@ -0,0 +1,887 @@ +{ + "swagger": "2.0", + "info": { + "contact": {} + }, + "paths": { + "/app/cdk/create-cdk-order": { + "post": { + "tags": [ + "Cdk App" + ], + "summary": "创建兑换订单", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.CreateCdkOrderReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.CreateCdkOrderResp" + } + } + } + ] + } + } + } + } + }, + "/app/cdk/deal-cdk-order": { + "post": { + "tags": [ + "Cdk App" + ], + "summary": "处理兑换订单", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.DealCdkOrderReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.DealCdkOrderResp" + } + } + } + ] + } + } + } + } + }, + "/app/cdk/get-cdk-type-by-coin-name": { + "post": { + "tags": [ + "Cdk App" + ], + "summary": "查询一个票券对应的 cdkType", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.GetCdkTypeByCoinNameReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.GetCdkTypeByCoinNameResp" + } + } + } + ] + } + } + } + } + }, + "/app/cdk/get-cdks-by-user-id": { + "post": { + "tags": [ + "Cdk App" + ], + "summary": "分页获得一个人拥有的 cdks", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.GetCdksByUserIdReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.GetCdksByUserIdResp" + } + } + } + ] + } + } + } + } + }, + "/backend/cdk/create-cdk-type": { + "post": { + "tags": [ + "Cdk 后台" + ], + "summary": "创建 CdkType", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.CreateCdkTypeReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.CreateCdkTypeResp" + } + } + } + ] + } + } + } + } + }, + "/backend/cdk/create-cdks": { + "post": { + "tags": [ + "Cdk 后台" + ], + "summary": "创建 Cdks", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.CreateCdksReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.CreateCdksResp" + } + } + } + ] + } + } + } + } + }, + "/backend/cdk/delete-cdk-types": { + "post": { + "tags": [ + "Cdk 后台" + ], + "summary": "删除 cdkTypes", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.DeleteCdkTypesReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.DeleteCdkTypesResp" + } + } + } + ] + } + } + } + } + }, + "/backend/cdk/delete-cdks": { + "post": { + "tags": [ + "Cdk 后台" + ], + "summary": "删除 cdks", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.DeleteCdksReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.DeleteCdksResp" + } + } + } + ] + } + } + } + } + }, + "/backend/cdk/exchange-cdks": { + "post": { + "tags": [ + "Cdk 后台" + ], + "summary": "兑换 cdks", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.ExchangeCdksReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.ExchangeCdksResp" + } + } + } + ] + } + } + } + } + }, + "/backend/cdk/get-cdk-types": { + "post": { + "tags": [ + "Cdk 查询" + ], + "summary": "分页获得 cdkType", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.GetCdkTypesReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.GetCdkTypesResp" + } + } + } + ] + } + } + } + } + }, + "/backend/cdk/get-cdks": { + "post": { + "tags": [ + "Cdk 查询" + ], + "summary": "分页获得 cdks", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.GetCdksReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.GetCdksResp" + } + } + } + ] + } + } + } + } + }, + "/backend/cdk/update-cdk-type": { + "post": { + "tags": [ + "Cdk 后台" + ], + "summary": "更新 cdkType", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/types.UpdateCdkTypeReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/types.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/types.UpdateCdkTypeResp" + } + } + } + ] + } + } + } + } + } + }, + "definitions": { + "types.Cdk": { + "type": "object", + "properties": { + "cdkContent": { + "type": "string" + }, + "cdkId": { + "type": "string" + }, + "cdkName": { + "type": "string" + }, + "cdkStatus": { + "type": "integer" + }, + "createTime": { + "type": "string" + }, + "exchangeTime": { + "type": "string" + }, + "id": { + "type": "string" + }, + "orderId": { + "type": "string" + }, + "userId": { + "type": "string" + } + } + }, + "types.CdkType": { + "type": "object", + "properties": { + "cdkAvailable": { + "description": "未发放的cdk数量", + "type": "integer" + }, + "cdkFrozen": { + "description": "冻结状态中的cdk数量", + "type": "integer" + }, + "cdkId": { + "type": "string" + }, + "cdkInfo": { + "type": "string" + }, + "cdkName": { + "type": "string" + }, + "cdkUsed": { + "description": "已发放的cdk数量", + "type": "integer" + }, + "coinName": { + "type": "string" + }, + "exchangeRate": { + "type": "integer" + } + } + }, + "types.CreateCdkOrderReq": { + "type": "object", + "required": [ + "cdkId", + "number" + ], + "properties": { + "cdkId": { + "description": "cdk 种类编号", + "type": "string" + }, + "number": { + "description": "兑换数量", + "type": "integer" + } + } + }, + "types.CreateCdkOrderResp": { + "type": "object", + "properties": { + "orderId": { + "description": "订单编号", + "type": "string" + } + } + }, + "types.CreateCdkTypeReq": { + "type": "object", + "required": [ + "coinName", + "exchangeRate" + ], + "properties": { + "cdkInfo": { + "type": "string" + }, + "cdkName": { + "type": "string" + }, + "coinName": { + "type": "string" + }, + "exchangeRate": { + "type": "integer" + } + } + }, + "types.CreateCdkTypeResp": { + "type": "object", + "properties": { + "cdkId": { + "type": "string" + } + } + }, + "types.CreateCdksReq": { + "type": "object", + "required": [ + "cdkContents", + "cdkId" + ], + "properties": { + "cdkContents": { + "type": "array", + "items": { + "type": "string" + } + }, + "cdkId": { + "type": "string" + } + } + }, + "types.CreateCdksResp": { + "type": "object" + }, + "types.DealCdkOrderReq": { + "type": "object", + "required": [ + "orderId", + "transferHash" + ], + "properties": { + "orderId": { + "description": "订单编号", + "type": "string" + }, + "result": { + "description": "处理结果", + "type": "boolean" + }, + "transferHash": { + "description": "转账记录 hash", + "type": "string" + } + } + }, + "types.DealCdkOrderResp": { + "type": "object" + }, + "types.DeleteCdkTypesReq": { + "type": "object", + "required": [ + "cdkIds" + ], + "properties": { + "cdkIds": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "types.DeleteCdkTypesResp": { + "type": "object" + }, + "types.DeleteCdksReq": { + "type": "object", + "required": [ + "ids" + ], + "properties": { + "ids": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "types.DeleteCdksResp": { + "type": "object" + }, + "types.ExchangeCdksReq": { + "type": "object", + "required": [ + "ids" + ], + "properties": { + "ids": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "types.ExchangeCdksResp": { + "type": "object" + }, + "types.GeneralResponse": { + "type": "object", + "properties": { + "data": { + "type": "object" + }, + "message": { + "type": "integer" + }, + "result": { + "type": "integer" + } + } + }, + "types.GetCdkTypeByCoinNameReq": { + "type": "object", + "required": [ + "coinName" + ], + "properties": { + "coinName": { + "type": "string" + } + } + }, + "types.GetCdkTypeByCoinNameResp": { + "type": "object", + "properties": { + "cdkAvailable": { + "description": "未发放的cdk数量", + "type": "integer" + }, + "cdkFrozen": { + "description": "冻结状态中的cdk数量", + "type": "integer" + }, + "cdkId": { + "type": "string" + }, + "cdkInfo": { + "type": "string" + }, + "cdkName": { + "type": "string" + }, + "cdkUsed": { + "description": "已发放的cdk数量", + "type": "integer" + }, + "coinName": { + "type": "string" + }, + "exchangeRate": { + "type": "integer" + } + } + }, + "types.GetCdkTypesReq": { + "type": "object", + "properties": { + "coinName": { + "type": "string" + }, + "page": { + "description": "页数", + "type": "integer" + }, + "pageSize": { + "description": "每页数量", + "type": "integer" + } + } + }, + "types.GetCdkTypesResp": { + "type": "object", + "properties": { + "cdkTypes": { + "type": "array", + "items": { + "$ref": "#/definitions/types.CdkType" + } + }, + "totalElements": { + "type": "integer" + }, + "totalPages": { + "type": "integer" + } + } + }, + "types.GetCdksByUserIdReq": { + "type": "object", + "properties": { + "page": { + "description": "页数", + "type": "integer" + }, + "pageSize": { + "description": "每页数量", + "type": "integer" + } + } + }, + "types.GetCdksByUserIdResp": { + "type": "object", + "properties": { + "cdks": { + "type": "array", + "items": { + "$ref": "#/definitions/types.Cdk" + } + }, + "totalElements": { + "type": "integer" + }, + "totalPages": { + "type": "integer" + } + } + }, + "types.GetCdksReq": { + "type": "object", + "required": [ + "cdkId" + ], + "properties": { + "cdkContent": { + "type": "string" + }, + "cdkId": { + "type": "string" + }, + "page": { + "description": "页数", + "type": "integer" + }, + "pageSize": { + "description": "每页数量", + "type": "integer" + } + } + }, + "types.GetCdksResp": { + "type": "object", + "properties": { + "cdks": { + "type": "array", + "items": { + "$ref": "#/definitions/types.Cdk" + } + }, + "totalElements": { + "type": "integer" + }, + "totalPages": { + "type": "integer" + } + } + }, + "types.UpdateCdkTypeReq": { + "type": "object", + "required": [ + "cdkId", + "cdkName", + "coinName", + "exchangeRate" + ], + "properties": { + "cdkId": { + "type": "string" + }, + "cdkName": { + "type": "string" + }, + "coinName": { + "type": "string" + }, + "exchangeRate": { + "type": "integer" + } + } + }, + "types.UpdateCdkTypeResp": { + "type": "object" + } + } +} \ No newline at end of file diff --git a/service/backend/docs/swagger.yaml b/service/backend/docs/swagger.yaml new file mode 100644 index 0000000..06c4998 --- /dev/null +++ b/service/backend/docs/swagger.yaml @@ -0,0 +1,540 @@ +definitions: + types.Cdk: + properties: + cdkContent: + type: string + cdkId: + type: string + cdkName: + type: string + cdkStatus: + type: integer + createTime: + type: string + exchangeTime: + type: string + id: + type: string + orderId: + type: string + userId: + type: string + type: object + types.CdkType: + properties: + cdkAvailable: + description: 未发放的cdk数量 + type: integer + cdkFrozen: + description: 冻结状态中的cdk数量 + type: integer + cdkId: + type: string + cdkInfo: + type: string + cdkName: + type: string + cdkUsed: + description: 已发放的cdk数量 + type: integer + coinName: + type: string + exchangeRate: + type: integer + type: object + types.CreateCdkOrderReq: + properties: + cdkId: + description: cdk 种类编号 + type: string + number: + description: 兑换数量 + type: integer + required: + - cdkId + - number + type: object + types.CreateCdkOrderResp: + properties: + orderId: + description: 订单编号 + type: string + type: object + types.CreateCdkTypeReq: + properties: + cdkInfo: + type: string + cdkName: + type: string + coinName: + type: string + exchangeRate: + type: integer + required: + - coinName + - exchangeRate + type: object + types.CreateCdkTypeResp: + properties: + cdkId: + type: string + type: object + types.CreateCdksReq: + properties: + cdkContents: + items: + type: string + type: array + cdkId: + type: string + required: + - cdkContents + - cdkId + type: object + types.CreateCdksResp: + type: object + types.DealCdkOrderReq: + properties: + orderId: + description: 订单编号 + type: string + result: + description: 处理结果 + type: boolean + transferHash: + description: 转账记录 hash + type: string + required: + - orderId + - transferHash + type: object + types.DealCdkOrderResp: + type: object + types.DeleteCdkTypesReq: + properties: + cdkIds: + items: + type: string + type: array + required: + - cdkIds + type: object + types.DeleteCdkTypesResp: + type: object + types.DeleteCdksReq: + properties: + ids: + items: + type: string + type: array + required: + - ids + type: object + types.DeleteCdksResp: + type: object + types.ExchangeCdksReq: + properties: + ids: + items: + type: string + type: array + required: + - ids + type: object + types.ExchangeCdksResp: + type: object + types.GeneralResponse: + properties: + data: + type: object + message: + type: integer + result: + type: integer + type: object + types.GetCdkTypeByCoinNameReq: + properties: + coinName: + type: string + required: + - coinName + type: object + types.GetCdkTypeByCoinNameResp: + properties: + cdkAvailable: + description: 未发放的cdk数量 + type: integer + cdkFrozen: + description: 冻结状态中的cdk数量 + type: integer + cdkId: + type: string + cdkInfo: + type: string + cdkName: + type: string + cdkUsed: + description: 已发放的cdk数量 + type: integer + coinName: + type: string + exchangeRate: + type: integer + type: object + types.GetCdkTypesReq: + properties: + coinName: + type: string + page: + description: 页数 + type: integer + pageSize: + description: 每页数量 + type: integer + type: object + types.GetCdkTypesResp: + properties: + cdkTypes: + items: + $ref: '#/definitions/types.CdkType' + type: array + totalElements: + type: integer + totalPages: + type: integer + type: object + types.GetCdksByUserIdReq: + properties: + page: + description: 页数 + type: integer + pageSize: + description: 每页数量 + type: integer + type: object + types.GetCdksByUserIdResp: + properties: + cdks: + items: + $ref: '#/definitions/types.Cdk' + type: array + totalElements: + type: integer + totalPages: + type: integer + type: object + types.GetCdksReq: + properties: + cdkContent: + type: string + cdkId: + type: string + page: + description: 页数 + type: integer + pageSize: + description: 每页数量 + type: integer + required: + - cdkId + type: object + types.GetCdksResp: + properties: + cdks: + items: + $ref: '#/definitions/types.Cdk' + type: array + totalElements: + type: integer + totalPages: + type: integer + type: object + types.UpdateCdkTypeReq: + properties: + cdkId: + type: string + cdkName: + type: string + coinName: + type: string + exchangeRate: + type: integer + required: + - cdkId + - cdkName + - coinName + - exchangeRate + type: object + types.UpdateCdkTypeResp: + type: object +info: + contact: {} +paths: + /app/cdk/create-cdk-order: + post: + parameters: + - description: MOCK + in: header + name: FZM-SIGNATURE + required: true + type: string + - description: body + in: body + name: data + schema: + $ref: '#/definitions/types.CreateCdkOrderReq' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/types.GeneralResponse' + - properties: + data: + $ref: '#/definitions/types.CreateCdkOrderResp' + type: object + summary: 创建兑换订单 + tags: + - Cdk App + /app/cdk/deal-cdk-order: + post: + parameters: + - description: MOCK + in: header + name: FZM-SIGNATURE + required: true + type: string + - description: body + in: body + name: data + schema: + $ref: '#/definitions/types.DealCdkOrderReq' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/types.GeneralResponse' + - properties: + data: + $ref: '#/definitions/types.DealCdkOrderResp' + type: object + summary: 处理兑换订单 + tags: + - Cdk App + /app/cdk/get-cdk-type-by-coin-name: + post: + parameters: + - description: body + in: body + name: data + schema: + $ref: '#/definitions/types.GetCdkTypeByCoinNameReq' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/types.GeneralResponse' + - properties: + data: + $ref: '#/definitions/types.GetCdkTypeByCoinNameResp' + type: object + summary: 查询一个票券对应的 cdkType + tags: + - Cdk App + /app/cdk/get-cdks-by-user-id: + post: + parameters: + - description: MOCK + in: header + name: FZM-SIGNATURE + required: true + type: string + - description: body + in: body + name: data + schema: + $ref: '#/definitions/types.GetCdksByUserIdReq' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/types.GeneralResponse' + - properties: + data: + $ref: '#/definitions/types.GetCdksByUserIdResp' + type: object + summary: 分页获得一个人拥有的 cdks + tags: + - Cdk App + /backend/cdk/create-cdk-type: + post: + parameters: + - description: body + in: body + name: data + schema: + $ref: '#/definitions/types.CreateCdkTypeReq' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/types.GeneralResponse' + - properties: + data: + $ref: '#/definitions/types.CreateCdkTypeResp' + type: object + summary: 创建 CdkType + tags: + - Cdk 后台 + /backend/cdk/create-cdks: + post: + parameters: + - description: body + in: body + name: data + schema: + $ref: '#/definitions/types.CreateCdksReq' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/types.GeneralResponse' + - properties: + data: + $ref: '#/definitions/types.CreateCdksResp' + type: object + summary: 创建 Cdks + tags: + - Cdk 后台 + /backend/cdk/delete-cdk-types: + post: + parameters: + - description: body + in: body + name: data + schema: + $ref: '#/definitions/types.DeleteCdkTypesReq' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/types.GeneralResponse' + - properties: + data: + $ref: '#/definitions/types.DeleteCdkTypesResp' + type: object + summary: 删除 cdkTypes + tags: + - Cdk 后台 + /backend/cdk/delete-cdks: + post: + parameters: + - description: body + in: body + name: data + schema: + $ref: '#/definitions/types.DeleteCdksReq' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/types.GeneralResponse' + - properties: + data: + $ref: '#/definitions/types.DeleteCdksResp' + type: object + summary: 删除 cdks + tags: + - Cdk 后台 + /backend/cdk/exchange-cdks: + post: + parameters: + - description: body + in: body + name: data + schema: + $ref: '#/definitions/types.ExchangeCdksReq' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/types.GeneralResponse' + - properties: + data: + $ref: '#/definitions/types.ExchangeCdksResp' + type: object + summary: 兑换 cdks + tags: + - Cdk 后台 + /backend/cdk/get-cdk-types: + post: + parameters: + - description: body + in: body + name: data + schema: + $ref: '#/definitions/types.GetCdkTypesReq' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/types.GeneralResponse' + - properties: + data: + $ref: '#/definitions/types.GetCdkTypesResp' + type: object + summary: 分页获得 cdkType + tags: + - Cdk 查询 + /backend/cdk/get-cdks: + post: + parameters: + - description: body + in: body + name: data + schema: + $ref: '#/definitions/types.GetCdksReq' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/types.GeneralResponse' + - properties: + data: + $ref: '#/definitions/types.GetCdksResp' + type: object + summary: 分页获得 cdks + tags: + - Cdk 查询 + /backend/cdk/update-cdk-type: + post: + parameters: + - description: body + in: body + name: data + schema: + $ref: '#/definitions/types.UpdateCdkTypeReq' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/types.GeneralResponse' + - properties: + data: + $ref: '#/definitions/types.UpdateCdkTypeResp' + type: object + summary: 更新 cdkType + tags: + - Cdk 后台 +swagger: "2.0" diff --git a/service/backend/midware/midware.go b/service/backend/midware/midware.go new file mode 100644 index 0000000..2154de4 --- /dev/null +++ b/service/backend/midware/midware.go @@ -0,0 +1,52 @@ +package midware + +import ( + "github.com/dgrijalva/jwt-go" + "github.com/gin-gonic/gin" + "gitlab.33.cn/chat/dtalk/pkg/api" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/service/backend/model" + "strings" +) + +func JWTAuthMiddleWare(flag bool, key string) gin.HandlerFunc { + return func(c *gin.Context) { + if flag == true { + c.Next() + return + } + authHeader := c.GetHeader("Authorization") + if len(authHeader) == 0 { + c.Set(api.ReqError, xerror.NewError(xerror.TokenError).SetExtMessage("token is empty")) + c.Abort() + return + } + stringArray := strings.SplitN(authHeader, " ", 2) + if !(len(stringArray) == 2 && stringArray[0] == "Bearer") { + c.Set(api.ReqError, xerror.NewError(xerror.TokenError).SetExtMessage("The token's format is wrong")) + c.Abort() + return + } + claims, err := parseToken(stringArray[1], key) + if err != nil { + c.Set(api.ReqError, err) + c.Abort() + return + } + c.Set("userName", claims.Username) + c.Next() + } +} + +func parseToken(tokenString string, key string) (*model.Claims, error) { + token, err := jwt.ParseWithClaims(tokenString, &model.Claims{}, func(token *jwt.Token) (i interface{}, err error) { + return []byte(key), nil + }) + if err != nil { + return nil, xerror.NewError(xerror.TokenError).SetExtMessage("invalid token") + } + if claims, ok := token.Claims.(*model.Claims); ok && token.Valid { + return claims, nil + } + return nil, xerror.NewError(xerror.TokenError).SetExtMessage("invalid token") +} diff --git a/service/backend/model/biz/cdkbiz.go b/service/backend/model/biz/cdkbiz.go new file mode 100644 index 0000000..c3421e1 --- /dev/null +++ b/service/backend/model/biz/cdkbiz.go @@ -0,0 +1,98 @@ +package biz + +import ( + "gitlab.33.cn/chat/dtalk/service/backend/model/db" + "gitlab.33.cn/chat/dtalk/service/backend/model/types" + "gitlab.33.cn/utils/go-kit/convert" + "time" +) + +// 业务模型 + +// CdkType cdk 种类 +type CdkType struct { + CdkId int64 `json:"cdkId,omitempty"` + CdkName string `json:"cdkName,omitempty"` + CoinName string `json:"coinName,omitempty"` + ExchangeRate int64 `json:"exchangeRate,omitempty"` + CdkInfo string `json:"cdkInfo,omitempty"` + // 未发放的cdk数量 + CdkAvailable int64 `json:"cdkAvailable,omitempty"` + // 已发放的cdk数量 + CdkUsed int64 `json:"cdkUsed,omitempty"` + // 冻结状态中的cdk数量 + CdkFrozen int64 `json:"cdkFrozen,omitempty"` +} + +func (cdkType *CdkType) ToTypes() *types.CdkType { + return &types.CdkType{ + CdkId: convert.ToString(cdkType.CdkId), + CdkName: cdkType.CdkName, + CoinName: cdkType.CoinName, + ExchangeRate: cdkType.ExchangeRate, + CdkInfo: cdkType.CdkInfo, + CdkAvailable: cdkType.CdkAvailable, + CdkUsed: cdkType.CdkUsed, + CdkFrozen: cdkType.CdkFrozen, + } +} + +// Cdk cdk 实例 +type Cdk struct { + Id int64 `json:"id,omitempty"` + CdkId int64 `json:"cdkId,omitempty"` + CdkContent string `json:"cdkContent,omitempty"` + UserId string `json:"userId,omitempty"` + CdkStatus int32 `json:"cdkStatus,omitempty"` + OrderId int64 `json:"orderId,omitempty"` + CreateTime int64 `json:"createTime"` + UpdateTime int64 + ExchangeTime int64 `json:"exchangeTime"` +} + +func (cdk *Cdk) ToTypes() *types.Cdk { + return &types.Cdk{ + Id: convert.ToString(cdk.Id), + CdkId: convert.ToString(cdk.CdkId), + CdkContent: cdk.CdkContent, + UserId: cdk.UserId, + CdkStatus: cdk.CdkStatus, + OrderId: convert.ToString(cdk.OrderId), + CreateTime: convert.ToString(cdk.CreateTime), + ExchangeTime: convert.ToString(cdk.ExchangeTime), + } +} + +func (cdk *Cdk) CheckUserId(userId string) bool { + return cdk.UserId == userId +} + +func (cdk *Cdk) CheckFrozen() bool { + return cdk.CdkStatus == db.CdkFrozen +} + +type CdkOrderMessage struct { + PersonId string + CdkId int64 + Number int64 + Done chan *CdkOrder +} + +func NewCdkOrderMessage(personId string, cdkId, number int64) *CdkOrderMessage { + return &CdkOrderMessage{ + PersonId: personId, + CdkId: cdkId, + Number: number, + Done: make(chan *CdkOrder), + } +} + +type CdkOrder struct { + Err error + OrderId int64 +} + +type ClearFrozenOrderMessage struct { + OrderId int64 + Deadline time.Duration +} diff --git a/service/backend/model/biz/tx.go b/service/backend/model/biz/tx.go new file mode 100644 index 0000000..ea62593 --- /dev/null +++ b/service/backend/model/biz/tx.go @@ -0,0 +1,8 @@ +package biz + +type TxResult struct { + Success bool + To string + Amount int64 + Symbol string +} diff --git a/service/backend/model/const.go b/service/backend/model/const.go new file mode 100644 index 0000000..1234516 --- /dev/null +++ b/service/backend/model/const.go @@ -0,0 +1,6 @@ +package model + +const ( + PlatformChat33Pro = "Chat33Pro" + Size = 20 +) diff --git a/service/backend/model/db/db.go b/service/backend/model/db/db.go new file mode 100644 index 0000000..b68a71d --- /dev/null +++ b/service/backend/model/db/db.go @@ -0,0 +1,47 @@ +package db + +// 数据库模型 +// 持久化模型 PO + +type TimeInfo struct { + CreateTime int64 + UpdateTime int64 + DeleteTime int64 +} + +type CdkType struct { + CdkId int64 + CdkName string + CdkInfo string + CoinName string + ExchangeRate int64 + TimeInfo +} + +type Cdk struct { + Id int64 + CdkId int64 + CdkContent string + UserId string + CdkStatus int32 + OrderId int64 + TimeInfo + ExchangeTime int64 +} + +type CdkOrder struct { + Id int64 + OrderId int64 + TransferHash string + UserId string + OrderStatus int32 + TimeInfo + ExchangeTime int64 +} + +const ( + CdkUnused = 0 + CdkFrozen = 1 + CdkUsed = 2 + CdkExchange = 3 +) diff --git a/service/backend/model/types/cdkhttp.go b/service/backend/model/types/cdkhttp.go new file mode 100644 index 0000000..7aeeb50 --- /dev/null +++ b/service/backend/model/types/cdkhttp.go @@ -0,0 +1,201 @@ +package types + +type GeneralResponse struct { + Result int `json:"result"` + Message int `json:"message"` + Data interface{} `json:"data"` +} + +// http 请求和返回 + +// CdkType cdk 种类 +type CdkType struct { + CdkId string `json:"cdkId"` + CdkName string `json:"cdkName"` + CoinName string `json:"coinName"` + ExchangeRate int64 `json:"exchangeRate"` + CdkInfo string `json:"cdkInfo"` + // 未发放的cdk数量 + CdkAvailable int64 `json:"cdkAvailable"` + // 已发放的cdk数量 + CdkUsed int64 `json:"cdkUsed"` + // 冻结状态中的cdk数量 + CdkFrozen int64 `json:"cdkFrozen"` +} + +// Cdk cdk 实例 +type Cdk struct { + Id string `json:"id"` + CdkId string `json:"cdkId"` + CdkName string `json:"cdkName"` + CdkContent string `json:"cdkContent"` + UserId string `json:"userId"` + CdkStatus int32 `json:"cdkStatus"` + OrderId string `json:"orderId"` + CreateTime string `json:"createTime"` + ExchangeTime string `json:"exchangeTime"` +} + +// PageInfo 分页信息 +type PageInfo struct { + // 页数 + Page int64 `json:"page"` + // 每页数量 + PageSize int64 `json:"pageSize"` +} + +// -----------------------------backend----------------------------- + +// CreateCdkTypeReq 创建 cdk 种类请求 +type CreateCdkTypeReq struct { + CdkName string `json:"cdkName"` + CoinName string `json:"coinName" binding:"required"` + ExchangeRate int64 `json:"exchangeRate" binding:"required"` + CdkInfo string `json:"cdkInfo"` +} + +// CreateCdkTypeResp 创建 cdk 种类响应 +type CreateCdkTypeResp struct { + CdkId string `json:"cdkId"` +} + +// GetCdkTypesReq 查询 cdk 种类请求 +type GetCdkTypesReq struct { + PageInfo + CoinName string `json:"coinName"` +} + +// GetCdkTypesResp 查询 cdk 种类响应 +type GetCdkTypesResp struct { + TotalElements int64 `json:"totalElements"` + TotalPages int64 `json:"totalPages"` + CdkTypes []*CdkType `json:"cdkTypes"` +} + +// CreateCdksReq 批量上传 cdk 请求 +type CreateCdksReq struct { + CdkId string `json:"cdkId" binding:"required"` + CdkContents []string `json:"cdkContents" binding:"required"` +} + +// CreateCdksResp 批量上传 cdk 响应 +type CreateCdksResp struct { +} + +// GetCdksReq 查询 cdk 实例请求 +type GetCdksReq struct { + PageInfo + CdkId string `json:"cdkId" binding:"required"` + CdkContent string `json:"cdkContent"` +} + +// GetCdksResp 查询 cdk 实例响应 +type GetCdksResp struct { + TotalElements int64 `json:"totalElements"` + TotalPages int64 `json:"totalPages"` + Cdks []*Cdk `json:"cdks"` +} + +// GetCdksWithCdkNameReq 根据 cdkName 查询 cdk 实例请求 +//type GetCdksWithCdkNameReq struct { +// PageInfo +// CdkName string `json:"cdkName"` +//} + +// GetCdksWithCdkNameResp 根据 cdkName 查询 cdk 实例响应 +//type GetCdksWithCdkNameResp struct { +// TotalElements int64 `json:"totalElements"` +// TotalPages int64 `json:"totalPages"` +// Cdks []*Cdk `json:"cdks"` +//} + +// DeleteCdksReq 删除 cdk 实例请求 +type DeleteCdksReq struct { + Ids []string `json:"ids" binding:"required"` +} + +// DeleteCdksResp 删除 cdk 实例响应 +type DeleteCdksResp struct { +} + +// DeleteCdkTypesReq 删除 cdkType 请求 +type DeleteCdkTypesReq struct { + CdkIds []string `json:"cdkIds" binding:"required"` +} + +// DeleteCdkTypesResp 删除 cdkType 响应 +type DeleteCdkTypesResp struct { +} + +type UpdateCdkTypeReq struct { + CdkId string `json:"cdkId" binding:"required"` + CdkName string `json:"cdkName" binding:"required"` + CoinName string `json:"coinName" binding:"required"` + ExchangeRate int64 `json:"exchangeRate" binding:"required"` +} + +type UpdateCdkTypeResp struct { +} + +type ExchangeCdksReq struct { + Ids []string `json:"ids" binding:"required"` +} + +type ExchangeCdksResp struct { +} + +// -----------------------------app----------------------------- + +// GetCdksByUserIdReq 查询某人拥有的 cdk 实例请求 +type GetCdksByUserIdReq struct { + PageInfo + //CdkId string `json:"cdkId" binding:"required"` + PersonId string `json:"-"` +} + +// GetCdksByUserIdResp 查询某人拥有的 cdk 实例响应 +type GetCdksByUserIdResp struct { + TotalElements int64 `json:"totalElements"` + TotalPages int64 `json:"totalPages"` + Cdks []*Cdk `json:"cdks"` +} + +// GetCdkTypeByCoinNameReq 根据票券名称查询对应的 cdk 信息请求 +type GetCdkTypeByCoinNameReq struct { + CoinName string `json:"coinName" binding:"required"` +} + +// GetCdkTypeByCoinNameResp 根据票券名称查询对应的 cdk 信息响应 +type GetCdkTypeByCoinNameResp struct { + *CdkType +} + +// CreateCdkOrderReq 创建兑换券订单请求 +type CreateCdkOrderReq struct { + PersonId string `json:"-"` + // cdk 种类编号 + CdkId string `json:"cdkId" binding:"required"` + // 兑换数量 + Number int64 `json:"number" binding:"required"` +} + +// CreateCdkOrderResp 创建兑换券订单响应 +type CreateCdkOrderResp struct { + // 订单编号 + OrderId string `json:"orderId"` +} + +// DealCdkOrderReq 处理兑换券订单请求 +type DealCdkOrderReq struct { + PersonId string `json:"-"` + // 订单编号 + OrderId string `json:"orderId" binding:"required"` + // 处理结果 + Result bool `json:"result"` + // 转账记录 hash + TransferHash string `json:"transferHash" binding:"required"` +} + +// DealCdkOrderResp 处理兑换券订单响应 +type DealCdkOrderResp struct { +} diff --git a/service/backend/model/version.go b/service/backend/model/version.go new file mode 100644 index 0000000..11bd0e2 --- /dev/null +++ b/service/backend/model/version.go @@ -0,0 +1,76 @@ +package model + +import ( + "encoding/json" + "github.com/dgrijalva/jwt-go" + "gitlab.33.cn/chat/dtalk/pkg/util" +) + +type Description []string + +func (desc *Description) ToString() string { + b, err := json.Marshal(desc) + if err != nil { + return "" + } + return string(b) +} + +func ConvertDescription(str string) (Description, error) { + var desc Description + err := json.Unmarshal([]byte(str), &desc) + if err != nil { + return nil, err + } + return desc, nil +} + +type VersionForm struct { + Id int64 `json:"id"` + Platform string `json:"platform"` + Status int32 `json:"status"` + DeviceType string `json:"deviceType"` + VersionName string `json:"versionName"` + VersionCode int64 `json:"versionCode"` + Url string `json:"url"` + Force bool `json:"force"` + Description Description `json:"description"` + OpeUser string `json:"opeUser"` + Md5 string `json:"md5"` + Size int64 `json:"size"` + UpdateTime int64 `json:"updateTime"` + CreateTime int64 `json:"createTime"` +} + +func ConvertVersionForm(record *map[string]string) (*VersionForm, error) { + description, err := ConvertDescription((*record)["description"]) + if err != nil { + return nil, err + } + return &VersionForm{ + Id: util.ToInt64((*record)["id"]), + Platform: (*record)["platform"], + Status: util.ToInt32((*record)["state"]), + DeviceType: (*record)["device_type"], + VersionName: (*record)["version_name"], + VersionCode: util.ToInt64((*record)["version_code"]), + Url: (*record)["download_url"], + Force: util.ToBool((*record)["force_update"]), + Description: description, + OpeUser: (*record)["ope_user"], + Md5: (*record)["md5"], + Size: util.ToInt64((*record)["size"]), + UpdateTime: util.ToInt64((*record)["update_time"]), + CreateTime: util.ToInt64((*record)["create_time"]), + }, nil +} + +type Claims struct { + Username string `json:"username"` + jwt.StandardClaims +} + +type UserInfo struct { + UserName string `json:"userName"` + Password string `json:"password"` +} diff --git a/service/backend/model/version_test.go b/service/backend/model/version_test.go new file mode 100644 index 0000000..0b13c89 --- /dev/null +++ b/service/backend/model/version_test.go @@ -0,0 +1,17 @@ +package model + +import ( + "encoding/json" + "testing" +) + +func Test_Marshal(t *testing.T) { + var desc Description = []string{"1", "2"} + + b, err := json.Marshal(&desc) + if err != nil { + t.Error(err) + return + } + t.Log("success", string(b)) +} diff --git a/service/backend/model/versionhttp.go b/service/backend/model/versionhttp.go new file mode 100644 index 0000000..a7f3622 --- /dev/null +++ b/service/backend/model/versionhttp.go @@ -0,0 +1,78 @@ +package model + +type VersionCreateRequest struct { + Platform string `json:"platform"` + Description []string `json:"description"` + Force bool `json:"force"` + Url string `json:"url"` + VersionCode int64 `json:"versionCode"` + VersionName string `json:"versionName"` + DeviceType string `json:"deviceType"` + OpeUser string `json:"opeUser"` + Md5 string `json:"md5"` + Size int64 `json:"size"` +} + +type VersionUpdateRequest struct { + Description []string `json:"description"` + Force bool `json:"force"` + Url string `json:"url"` + VersionCode int64 `json:"versionCode"` + VersionName string `json:"versionName"` + Id int64 `json:"id"` + OpeUser string `json:"opeUser"` + Md5 string `json:"md5"` + Size int64 `json:"size"` +} + +type VersionChangeStatusRequest struct { + Id int64 `json:"id"` + OpeUser string `json:"opeUser"` +} + +type VersionCheckAndUpdateRequest struct { + VersionCode int64 `form:"versionCode" json:"versionCode"` + DeviceType string `json:"deviceType"` +} + +type GetVersionListRequest struct { + Page int64 `json:"page"` + Platform string `json:"platform"` + DeviceType string `json:"deviceType"` +} + +type GetTokenRequest struct { + UserName string `form:"userName" json:"userName"` + Password string `form:"password" json:"password"` +} + +type VersionCreateResponse struct { + Version VersionForm `json:"version"` +} + +type VersionUpdateResponse struct { + Version VersionForm `json:"version"` +} + +type VersionChangeStatusResponse struct { + VersionList []VersionForm `json:"versionList"` +} + +type VersionCheckAndUpdateResponse struct { + VersionForm +} + +type GetVersionListResponse struct { + TotalElements int64 `json:"totalElements"` + TotalPages int64 `json:"totalPages"` + VersionList []VersionForm `json:"versionList"` +} + +type GetTokenResponse struct { + UserInfo UserInfoResponse `json:"userInfo"` +} + +type UserInfoResponse struct { + UserName string `json:"userName"` + Token string `json:"token"` +} diff --git a/service/backend/server/http/cdk.go b/service/backend/server/http/cdk.go new file mode 100644 index 0000000..8dd593a --- /dev/null +++ b/service/backend/server/http/cdk.go @@ -0,0 +1,285 @@ +package http + +import ( + "github.com/gin-gonic/gin" + "gitlab.33.cn/chat/dtalk/pkg/api" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/service/backend/model/types" +) + +//userId, ok := c.Get(api.Address) +// if !ok { +// c.Set(api.ReqError, xerror.NewError(xerror.SignatureInvalid)) +// return +// } +// @Summary 创建 CdkType +// @Author chy@33.cn +// @Tags Cdk 后台 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.CreateGroupRequest false "body" +// @Success 200 {object} types.GeneralResponse{data=types.CreateGroupResponse} +// @Router /app/create-group [post] + +// CreateCdkTypeHandler +// @Summary 创建 CdkType +// @Author chy@33.cn +// @Tags Cdk 后台 +// @Param data body types.CreateCdkTypeReq false "body" +// @Success 200 {object} types.GeneralResponse{data=types.CreateCdkTypeResp} +// @Router /backend/cdk/create-cdk-type [post] +func CreateCdkTypeHandler(c *gin.Context) { + req := &types.CreateCdkTypeReq{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + res, err := svc.CdkService.CreateCdkTypeSvc(req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) +} + +// CreateCdksHandler +// @Summary 创建 Cdks +// @Author chy@33.cn +// @Tags Cdk 后台 +// @Param data body types.CreateCdksReq false "body" +// @Success 200 {object} types.GeneralResponse{data=types.CreateCdksResp} +// @Router /backend/cdk/create-cdks [post] +func CreateCdksHandler(c *gin.Context) { + req := &types.CreateCdksReq{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + res, err := svc.CdkService.CreateCdksSvc(req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) +} + +// GetCdkTypesHandler +// @Summary 分页获得 cdkType +// @Author chy@33.cn +// @Tags Cdk 查询 +// @Param data body types.GetCdkTypesReq false "body" +// @Success 200 {object} types.GeneralResponse{data=types.GetCdkTypesResp} +// @Router /backend/cdk/get-cdk-types [post] +func GetCdkTypesHandler(c *gin.Context) { + req := &types.GetCdkTypesReq{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + res, err := svc.CdkService.GetCdkTypesSvc(req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) +} + +// GetCdksHandler +// @Summary 分页获得 cdks +// @Author chy@33.cn +// @Tags Cdk 查询 +// @Param data body types.GetCdksReq false "body" +// @Success 200 {object} types.GeneralResponse{data=types.GetCdksResp} +// @Router /backend/cdk/get-cdks [post] +func GetCdksHandler(c *gin.Context) { + req := &types.GetCdksReq{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + res, err := svc.CdkService.GetCdksSvc(req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) +} + +// DeleteCdksHandler +// @Summary 删除 cdks +// @Author chy@33.cn +// @Tags Cdk 后台 +// @Param data body types.DeleteCdksReq false "body" +// @Success 200 {object} types.GeneralResponse{data=types.DeleteCdksResp} +// @Router /backend/cdk/delete-cdks [post] +func DeleteCdksHandler(c *gin.Context) { + req := &types.DeleteCdksReq{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + res, err := svc.CdkService.DeleteCdksSvc(req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) +} + +// DeleteCdkTypesHandler +// @Summary 删除 cdkTypes +// @Author chy@33.cn +// @Tags Cdk 后台 +// @Param data body types.DeleteCdkTypesReq false "body" +// @Success 200 {object} types.GeneralResponse{data=types.DeleteCdkTypesResp} +// @Router /backend/cdk/delete-cdk-types [post] +func DeleteCdkTypesHandler(c *gin.Context) { + req := &types.DeleteCdkTypesReq{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + res, err := svc.CdkService.DeleteCdkTypesSvc(req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) +} + +// ExchangeCdksHandler +// @Summary 兑换 cdks +// @Author chy@33.cn +// @Tags Cdk 后台 +// @Param data body types.ExchangeCdksReq false "body" +// @Success 200 {object} types.GeneralResponse{data=types.ExchangeCdksResp} +// @Router /backend/cdk/exchange-cdks [post] +func ExchangeCdksHandler(c *gin.Context) { + req := &types.ExchangeCdksReq{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + res, err := svc.CdkService.ExchangeCdksSvc(req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) +} + +// UpdateCdkTypeHandler +// @Summary 更新 cdkType +// @Author chy@33.cn +// @Tags Cdk 后台 +// @Param data body types.UpdateCdkTypeReq false "body" +// @Success 200 {object} types.GeneralResponse{data=types.UpdateCdkTypeResp} +// @Router /backend/cdk/update-cdk-type [post] +func UpdateCdkTypeHandler(c *gin.Context) { + req := &types.UpdateCdkTypeReq{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + res, err := svc.CdkService.UpdateCdkTypeSvc(req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) +} + +// GetCdksByUserIdHandler +// @Summary 分页获得一个人拥有的 cdks +// @Author chy@33.cn +// @Tags Cdk App +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.GetCdksByUserIdReq false "body" +// @Success 200 {object} types.GeneralResponse{data=types.GetCdksByUserIdResp} +// @Router /app/cdk/get-cdks-by-user-id [post] +func GetCdksByUserIdHandler(c *gin.Context) { + req := &types.GetCdksByUserIdReq{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + userId, ok := c.Get(api.Address) + if !ok { + c.Set(api.ReqError, xerror.NewError(xerror.SignatureInvalid)) + return + } + req.PersonId = userId.(string) + + res, err := svc.CdkService.GetCdksByUserIdSvc(req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) +} + +// GetCdkTypeByCoinNameHandler +// @Summary 查询一个票券对应的 cdkType +// @Author chy@33.cn +// @Tags Cdk App +// @Param data body types.GetCdkTypeByCoinNameReq false "body" +// @Success 200 {object} types.GeneralResponse{data=types.GetCdkTypeByCoinNameResp} +// @Router /app/cdk/get-cdk-type-by-coin-name [post] +func GetCdkTypeByCoinNameHandler(c *gin.Context) { + req := &types.GetCdkTypeByCoinNameReq{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + res, err := svc.CdkService.GetCdkTypeByCoinNameSvc(req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) +} + +// CreateCdkOrderHandler +// @Summary 创建兑换订单 +// @Author chy@33.cn +// @Tags Cdk App +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.CreateCdkOrderReq false "body" +// @Success 200 {object} types.GeneralResponse{data=types.CreateCdkOrderResp} +// @Router /app/cdk/create-cdk-order [post] +func CreateCdkOrderHandler(c *gin.Context) { + req := &types.CreateCdkOrderReq{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + userId, ok := c.Get(api.Address) + if !ok { + c.Set(api.ReqError, xerror.NewError(xerror.SignatureInvalid)) + return + } + req.PersonId = userId.(string) + + res, err := svc.CdkService.CreateCdkOrderSvc(req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) +} + +// DealCdkOrderHandler +// @Summary 处理兑换订单 +// @Author chy@33.cn +// @Tags Cdk App +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.DealCdkOrderReq false "body" +// @Success 200 {object} types.GeneralResponse{data=types.DealCdkOrderResp} +// @Router /app/cdk/deal-cdk-order [post] +func DealCdkOrderHandler(c *gin.Context) { + req := &types.DealCdkOrderReq{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + userId, ok := c.Get(api.Address) + if !ok { + c.Set(api.ReqError, xerror.NewError(xerror.SignatureInvalid)) + return + } + req.PersonId = userId.(string) + + res, err := svc.CdkService.DealCdkOrderSvc(req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) +} diff --git a/service/backend/server/http/http.go b/service/backend/server/http/http.go new file mode 100644 index 0000000..6cb9e46 --- /dev/null +++ b/service/backend/server/http/http.go @@ -0,0 +1,125 @@ +package http + +import ( + "github.com/gin-gonic/gin" + "github.com/inconshreveable/log15" + swaggerFiles "github.com/swaggo/files" + ginSwagger "github.com/swaggo/gin-swagger" + "gitlab.33.cn/chat/dtalk/pkg/api" + "gitlab.33.cn/chat/dtalk/service/backend/config" + _ "gitlab.33.cn/chat/dtalk/service/backend/docs" + "gitlab.33.cn/chat/dtalk/service/backend/midware" + "gitlab.33.cn/chat/dtalk/service/backend/service" + "net/http" +) + +var ( + svc *service.Service + log = log15.New("module", "backend/http") +) + +func Init(s *service.Service) *http.Server { + addr := s.Config().Server.Addr + if s.Config().Env != "debug" { + gin.SetMode(gin.ReleaseMode) + } + engine := Default() + InitService(s) + SetupEngine(engine) + + if s.Config().CdkMod { + setCdkRoute(engine) + } + + srv := &http.Server{ + Addr: addr, + Handler: engine, + } + go func() { + // service connections + if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Error("engineInner.Start() error(%v)", err) + panic(err) + } + }() + return srv +} + +// Default returns an Engine instance with the Logger and Recovery middleware already attached. +func Default() *gin.Engine { + router := gin.New() + // LoggerWithFormatter middleware will write the logs to gin.DefaultWriter + // By default gin.DefaultWriter = os.Stdout + router.Use(gin.LoggerWithFormatter(api.Chat33GinLogFormatter)) + router.Use(gin.Recovery()) + return router +} + +func InitService(s *service.Service) { + svc = s +} + +func SetupEngine(e *gin.Engine) *gin.Engine { + { + app := e.Group("/app", api.RespMiddleWare()) + version := app.Group("/version") + //获取服务器列表 + version.Use(api.HeaderMiddleWare()) + { + version.POST("/check", CheckAndUpdateVersion) + } + } + { + backend := e.Group("/backend", api.RespMiddleWare()) + token := backend.Group("/user") + { + token.POST("/login", GetToken) + } + version := backend.Group("/version") + version.Use(midware.JWTAuthMiddleWare(config.Conf.Debug.Flag, config.Conf.Release.Key)) + { + version.POST("/create", CreateVersion) + version.PUT("/update", UpdateVersion) + version.PUT("/change-status", ChangeVersionStatus) + version.GET("/list", GetVersionList) + } + } + + // swagger 文档接口 + e.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) + return e +} + +func setCdkRoute(e *gin.Engine) *gin.Engine { + { + app := e.Group("/app", api.RespMiddleWare()) + + // cdk app + appCkd := app.Group("/cdk") + appCkd.Use(api.AuthMiddleWare()) + { + appCkd.POST("get-cdks-by-user-id", GetCdksByUserIdHandler) + appCkd.POST("get-cdk-type-by-coin-name", GetCdkTypeByCoinNameHandler) + appCkd.POST("create-cdk-order", CreateCdkOrderHandler) + appCkd.POST("deal-cdk-order", DealCdkOrderHandler) + } + } + { + backend := e.Group("/backend", api.RespMiddleWare()) + + // cdk backend + bakCdk := backend.Group("/cdk") + bakCdk.POST("get-cdk-types", GetCdkTypesHandler) + bakCdk.POST("get-cdks", GetCdksHandler) + bakCdk.Use(midware.JWTAuthMiddleWare(config.Conf.Debug.Flag, config.Conf.Release.Key)) + { + bakCdk.POST("create-cdk-type", CreateCdkTypeHandler) + bakCdk.POST("create-cdks", CreateCdksHandler) + bakCdk.POST("delete-cdks", DeleteCdksHandler) + bakCdk.POST("delete-cdk-types", DeleteCdkTypesHandler) + bakCdk.POST("update-cdk-type", UpdateCdkTypeHandler) + bakCdk.POST("exchange-cdks", ExchangeCdksHandler) + } + } + return e +} diff --git a/service/backend/server/http/version.go b/service/backend/server/http/version.go new file mode 100644 index 0000000..80125f4 --- /dev/null +++ b/service/backend/server/http/version.go @@ -0,0 +1,103 @@ +package http + +import ( + "github.com/gin-gonic/gin" + "gitlab.33.cn/chat/dtalk/pkg/api" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/pkg/util" + "gitlab.33.cn/chat/dtalk/service/backend/model" +) + +//get all nodes +//func CheckVersion(c *gin.Context) { +// ret := svc.CheckVersion(c.MustGet(api.DeviceType).(string)) +// c.Set(api.ReqResult, ret) +// c.Set(api.ReqError, nil) +//} + +func CreateVersion(c *gin.Context) { + request := model.VersionCreateRequest{} + err := c.ShouldBind(&request) + request.OpeUser = c.GetString("userName") + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage("ShouldBind"+err.Error())) + return + } + + ret, err := svc.CreateVersion(&request) + c.Set(api.ReqResult, ret) + c.Set(api.ReqError, err) +} + +func UpdateVersion(c *gin.Context) { + request := model.VersionUpdateRequest{} + err := c.ShouldBind(&request) + request.OpeUser = c.GetString("userName") + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage("ShouldBind"+err.Error())) + return + } + + ret, err := svc.UpdateVersion(&request) + c.Set(api.ReqResult, ret) + c.Set(api.ReqError, err) +} + +func ChangeVersionStatus(c *gin.Context) { + request := model.VersionChangeStatusRequest{} + err := c.ShouldBind(&request) + request.OpeUser = c.GetString("userName") + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage("ShouldBind"+err.Error())) + return + } + + err = svc.ChangeVersionStatus(&request) + c.Set(api.ReqResult, nil) + c.Set(api.ReqError, err) + +} + +func GetVersionList(c *gin.Context) { + request := model.GetVersionListRequest{} + request.Platform = c.DefaultQuery("platform", "%") + request.DeviceType = c.DefaultQuery("deviceType", "%") + request.Page = util.ToInt64(c.DefaultQuery("page", "0")) + ret, err := svc.GetVersionList(&request) + c.Set(api.ReqResult, ret) + c.Set(api.ReqError, err) +} + +func CheckAndUpdateVersion(c *gin.Context) { + request := model.VersionCheckAndUpdateRequest{} + err := c.ShouldBind(&request) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage("ShouldBind"+err.Error())) + return + } + deviceType, ok := c.Get(api.DeviceType) + if !ok { + c.Set(api.ReqError, xerror.NewError(xerror.DeviceTypeError)) + return + } + request.DeviceType = deviceType.(string) + + ret, err := svc.CheckAndUpdateVersion(&request) + c.Set(api.ReqResult, ret) + c.Set(api.ReqError, err) + +} + +func GetToken(c *gin.Context) { + request := model.GetTokenRequest{} + err := c.ShouldBind(&request) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage("ShouldBind"+err.Error())) + return + } + + ret, err := svc.GetToken(&request) + c.Set(api.ReqResult, ret) + c.Set(api.ReqError, err) + +} diff --git a/service/backend/service/cdk/cleanfrozenorder.go b/service/backend/service/cdk/cleanfrozenorder.go new file mode 100644 index 0000000..df56393 --- /dev/null +++ b/service/backend/service/cdk/cleanfrozenorder.go @@ -0,0 +1,57 @@ +package cdk + +import ( + "gitlab.33.cn/chat/dtalk/service/backend/model/biz" + "time" +) + +func (s *ServiceContent) CleanFrozenOrder() { + for range time.Tick(10 * time.Minute) { + // get frozen cdk + cdks, err := s.getFrozenCdks() + if err != nil { + continue + } + + nowTime := time.Now().UnixNano() / 1e6 + subTime := time.Now().Add(5*time.Minute).UnixNano()/1e6 - nowTime + OrderSet := make(map[int64]bool, 0) + for _, cdk := range cdks { + if nowTime-cdk.UpdateTime > subTime { + OrderSet[cdk.OrderId] = true + } + } + + for k, _ := range OrderSet { + err := s.dao.CleanFrozenCdks(k) + if err != nil { + s.log.Err(err).Int64("orderId", k).Msg("CleanFrozenCdks Error") + } + } + } +} + +func (s *ServiceContent) getFrozenCdks() ([]*biz.Cdk, error) { + cdkPos, err := s.dao.GetFrozenCdks() + if err != nil { + return nil, err + } + + cdks := make([]*biz.Cdk, 0, len(cdkPos)) + for _, cdkPo := range cdkPos { + cdk := &biz.Cdk{ + Id: cdkPo.Id, + CdkId: cdkPo.CdkId, + CdkContent: cdkPo.CdkContent, + UserId: cdkPo.UserId, + CdkStatus: cdkPo.CdkStatus, + OrderId: cdkPo.OrderId, + CreateTime: cdkPo.CreateTime, + UpdateTime: cdkPo.UpdateTime, + ExchangeTime: cdkPo.ExchangeTime, + } + cdks = append(cdks, cdk) + } + + return cdks, nil +} diff --git a/service/backend/service/cdk/common.go b/service/backend/service/cdk/common.go new file mode 100644 index 0000000..18e6047 --- /dev/null +++ b/service/backend/service/cdk/common.go @@ -0,0 +1,147 @@ +package cdk + +import ( + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/service/backend/model/biz" +) + +// GetCdkTypeByCoinName 通过 coinName 查询 cdkType +func (s *ServiceContent) GetCdkTypeByCoinName(coinName string) (*biz.CdkType, error) { + // 通过 coinName 查询 cdkType 信息 + cdkTypePos, err := s.dao.GetCdkTypesWithCoinName(coinName) + if err != nil { + return nil, err + } + if len(cdkTypePos) == 0 { + return nil, xerror.NewError(xerror.CdkCoinNameErr) + } + cdkTypePo := cdkTypePos[0] + + // 通过 cdkId 查询各种状态的 cdk 数量 + unused, frozen, used, err := s.dao.GetCdksCount(cdkTypePo.CdkId) + if err != nil { + return nil, err + } + + return &biz.CdkType{ + CdkId: cdkTypePo.CdkId, + CdkName: cdkTypePo.CdkName, + CoinName: cdkTypePo.CoinName, + ExchangeRate: cdkTypePo.ExchangeRate, + CdkInfo: cdkTypePo.CdkInfo, + CdkAvailable: unused, + CdkUsed: used, + CdkFrozen: frozen, + }, nil +} + +// GetCdkTypes 分页查询 CdkType 列表 +func (s *ServiceContent) GetCdkTypes(coinName string, page, pageSize int64) ([]*biz.CdkType, int64, int64, error) { + cdkTypePos, totalElements, totalPages, err := s.dao.GetCdkTypes(coinName, page, pageSize) + if err != nil { + return nil, 0, 0, err + } + + cdkTypes := make([]*biz.CdkType, 0, len(cdkTypePos)) + for _, cdkTypePo := range cdkTypePos { + // 通过 cdkId 查询各种状态的 cdk 数量 + unused, frozen, used, err := s.dao.GetCdksCount(cdkTypePo.CdkId) + if err != nil { + return nil, 0, 0, err + } + + cdkType := &biz.CdkType{ + CdkId: cdkTypePo.CdkId, + CdkName: cdkTypePo.CdkName, + CoinName: cdkTypePo.CoinName, + ExchangeRate: cdkTypePo.ExchangeRate, + CdkInfo: cdkTypePo.CdkInfo, + CdkAvailable: unused, + CdkUsed: used, + CdkFrozen: frozen, + } + + cdkTypes = append(cdkTypes, cdkType) + } + return cdkTypes, totalElements, totalPages, nil +} + +// GetCdks 分页查询 cdk 列表 +func (s *ServiceContent) GetCdks(cdkId int64, cdkContent string, page, pageSize int64) ([]*biz.Cdk, int64, int64, error) { + cdkPos, totalElements, totalPages, err := s.dao.GetCdks(cdkId, cdkContent, page, pageSize) + if err != nil { + return nil, 0, 0, err + } + + cdks := make([]*biz.Cdk, 0, len(cdkPos)) + for _, cdkPo := range cdkPos { + cdk := &biz.Cdk{ + Id: cdkPo.Id, + CdkId: cdkPo.CdkId, + CdkContent: cdkPo.CdkContent, + UserId: cdkPo.UserId, + CdkStatus: cdkPo.CdkStatus, + OrderId: cdkPo.OrderId, + CreateTime: cdkPo.CreateTime, + ExchangeTime: cdkPo.ExchangeTime, + } + cdks = append(cdks, cdk) + } + + return cdks, totalElements, totalPages, nil +} + +// GetCdkTypeByCdkId 通过 cdkId 查询 cdkType +func (s *ServiceContent) GetCdkTypeByCdkId(cdkId int64) (*biz.CdkType, error) { + cdkTypePo, err := s.dao.GetCdkType(cdkId) + if err != nil { + return nil, err + } + + if cdkTypePo == nil { + return nil, xerror.NewError(xerror.CodeInnerError) + } + + unused, frozen, used, err := s.dao.GetCdksCount(cdkTypePo.CdkId) + if err != nil { + return nil, err + } + + cdkType := &biz.CdkType{ + CdkId: cdkTypePo.CdkId, + CdkName: cdkTypePo.CdkName, + CoinName: cdkTypePo.CoinName, + ExchangeRate: cdkTypePo.ExchangeRate, + CdkInfo: cdkTypePo.CdkInfo, + CdkAvailable: unused, + CdkUsed: used, + CdkFrozen: frozen, + } + + return cdkType, nil +} + +// GetCdksWithCdkName 根据cdkName分页查询 cdk 列表 +//func (s *ServiceContent) GetCdksWithCdkName(cdkName string, page, pageSize int64) ([]*biz.Cdk, int64, int64, error) { +// cdkPos, totalElements, totalPages, err := s.dao.GetCdksByCdkName(cdkName, page, pageSize) +// if err != nil { +// return nil, 0, 0, err +// } +// +// cdks := make([]*biz.Cdk, 0, len(cdkPos)) +// for _, cdkPo := range cdkPos { +// cdk := &biz.Cdk{ +// Id: cdkPo.Id, +// CdkId: cdkPo.CdkId, +// CdkContent: cdkPo.CdkContent, +// UserId: cdkPo.UserId, +// CdkStatus: cdkPo.CdkStatus, +// OrderId: cdkPo.OrderId, +// CreateTime: cdkPo.CreateTime, +// ExchangeTime: cdkPo.ExchangeTime, +// } +// cdks = append(cdks, cdk) +// } +// +// return cdks, totalElements, totalPages, nil +//} diff --git a/service/backend/service/cdk/createcdkorder.go b/service/backend/service/cdk/createcdkorder.go new file mode 100644 index 0000000..11d7fbc --- /dev/null +++ b/service/backend/service/cdk/createcdkorder.go @@ -0,0 +1,158 @@ +package cdk + +import ( + "fmt" + "github.com/pkg/errors" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/service/backend/model/biz" + "gitlab.33.cn/chat/dtalk/service/backend/model/types" + "gitlab.33.cn/utils/go-kit/convert" +) + +// ListenOrder 监听创建订单消息队列 +func (s *ServiceContent) ListenOrder() { + // 单线程模型, 保证发号正确 + for { + select { + case msg := <-s.CdkOrderMessage: + + msg.Done <- s.createOrder(msg) + } + } +} + +// createOrder 处理创建订单消息 +func (s *ServiceContent) createOrder(msg *biz.CdkOrderMessage) *biz.CdkOrder { + s.log.Info().Str("PersonId", msg.PersonId). + Int64("CdkId", msg.CdkId). + Int64("Number", msg.Number). + Msg("ListenOrder") + + // 获得 orderId + orderId, err := s.idGenRPCClient.GetID() + if err != nil { + return &biz.CdkOrder{ + Err: err, + OrderId: 0, + } + } + + if msg.Number == 0 { + return &biz.CdkOrder{ + Err: errors.New("number is zero"), + OrderId: 0, + } + } + + // 判断这个人这种优惠券的数量 + count, err := s.dao.GetCdksCountByUserIdAndCdkId(msg.CdkId, msg.PersonId) + if err != nil { + return &biz.CdkOrder{ + Err: err, + OrderId: 0, + } + } + + if count+msg.Number > s.CdkMaxNumber { + return &biz.CdkOrder{ + Err: xerror.NewError(xerror.CdkMaxNumberErr).SetExtMessage(fmt.Sprintf("已有 %d 张, 将兑换 %d 张, 超出 %d 张", count, msg.Number, s.CdkMaxNumber)), + OrderId: 0, + } + } + + // 得到 n 个未发放的 cdk + cdks, err := s.GetUnusedCdks(msg.CdkId, msg.Number) + if err != nil { + return &biz.CdkOrder{ + Err: err, + OrderId: 0, + } + } + + // 冻结 cdk + err = s.FrozenCdks(cdks, msg.PersonId, orderId) + if err != nil { + return &biz.CdkOrder{ + Err: err, + OrderId: 0, + } + } + + // 加入定时清理订单任务队列 + + return &biz.CdkOrder{ + Err: nil, + OrderId: orderId, + } +} + +// CreateCdkOrderSvc 创建订单 Svc +func (s *ServiceContent) CreateCdkOrderSvc(req *types.CreateCdkOrderReq) (resp *types.CreateCdkOrderResp, err error) { + personId := req.PersonId + cdkId := convert.ToInt64(req.CdkId) + number := req.Number + done := make(chan *biz.CdkOrder) + + defer func() { + if err != nil { + s.log.Err(err).Interface("req", req).Str("personId", personId).Msg("CreateCdkOrderSvc") + } else { + s.log.Info().Interface("req", req).Str("personId", personId).Msg("CreateCdkOrderSvc") + } + }() + + s.CdkOrderMessage <- &biz.CdkOrderMessage{ + PersonId: personId, + CdkId: cdkId, + Number: number, + Done: done, + } + + res := <-done + if res.Err != nil { + return nil, res.Err + } + + return &types.CreateCdkOrderResp{ + OrderId: convert.ToString(res.OrderId), + }, nil +} + +// GetUnusedCdks 按顺序获得未发放的 cdk +func (s *ServiceContent) GetUnusedCdks(cdkId int64, number int64) ([]*biz.Cdk, error) { + cdkPos, err := s.dao.GetUnusedCdks(cdkId, number) + if err != nil { + return nil, err + } + + if len(cdkPos) < int(number) { + return nil, xerror.NewError(xerror.CdkOutOfStock) + } + + cdks := make([]*biz.Cdk, 0, len(cdkPos)) + for _, cdkPo := range cdkPos { + cdk := &biz.Cdk{ + Id: cdkPo.Id, + CdkId: cdkPo.CdkId, + CdkContent: cdkPo.CdkContent, + UserId: cdkPo.UserId, + CdkStatus: cdkPo.CdkStatus, + OrderId: cdkPo.OrderId, + } + cdks = append(cdks, cdk) + } + + return cdks, nil +} + +// FrozenCdks 冻结 cdk +func (s *ServiceContent) FrozenCdks(cdks []*biz.Cdk, userId string, orderId int64) error { + ids := make([]int64, 0, len(cdks)) + for _, cdk := range cdks { + ids = append(ids, cdk.Id) + } + + return s.dao.FrozenCdksStatus(ids, userId, orderId) +} + +// TODO : 处理超时为完成支付的订单 diff --git a/service/backend/service/cdk/createcdkorder_test.go b/service/backend/service/cdk/createcdkorder_test.go new file mode 100644 index 0000000..a73add1 --- /dev/null +++ b/service/backend/service/cdk/createcdkorder_test.go @@ -0,0 +1,35 @@ +package cdk + +import ( + "gitlab.33.cn/chat/dtalk/service/backend/model/types" + "gitlab.33.cn/utils/go-kit/convert" + "sync" + "testing" +) + +func TestCreateCdkOrder(t *testing.T) { + wg := sync.WaitGroup{} + for i := 0; i < 10; i++ { + wg.Add(1) + go func(i int) { + t.Log("start req", convert.ToString(i)) + req := &types.CreateCdkOrderReq{ + PersonId: convert.ToString(i), + CdkId: convert.ToString(i), + Number: convert.ToInt64(i), + } + + resp, err := srv.CreateCdkOrderSvc(req) + if err != nil { + t.Log(err) + } else { + t.Log(resp) + } + t.Log("end req", convert.ToString(i)) + wg.Done() + }(i) + + } + wg.Wait() + +} diff --git a/service/backend/service/cdk/createcdks.go b/service/backend/service/cdk/createcdks.go new file mode 100644 index 0000000..a881ec3 --- /dev/null +++ b/service/backend/service/cdk/createcdks.go @@ -0,0 +1,58 @@ +package cdk + +import ( + "gitlab.33.cn/chat/dtalk/service/backend/model/db" + "gitlab.33.cn/chat/dtalk/service/backend/model/types" + "gitlab.33.cn/utils/go-kit/convert" + "time" +) + +func (s *ServiceContent) CreateCdksSvc(req *types.CreateCdksReq) (res *types.CreateCdksResp, err error) { + defer func() { + if err != nil { + s.log.Err(err).Interface("req", req).Msg("CreateCdksSvc") + } else { + s.log.Info().Interface("req", req).Msg("CreateCdksSvc") + } + }() + + cdkId := convert.ToInt64(req.CdkId) + + // 判断 cdkId 是否存在 + _, err = s.GetCdkTypeByCdkId(cdkId) + if err != nil { + return nil, err + } + + for _, c := range req.CdkContents { + ok, err := s.dao.CheckCdkExist(cdkId, c) + if err != nil || ok { + continue + } + + id, err := s.idGenRPCClient.GetID() + if err != nil { + continue + } + cdk := &db.Cdk{ + Id: id, + CdkId: cdkId, + CdkContent: c, + UserId: "", + CdkStatus: 0, + OrderId: 0, + TimeInfo: db.TimeInfo{ + CreateTime: time.Now().UnixNano() / 1e6, + UpdateTime: time.Now().UnixNano() / 1e6, + DeleteTime: 0, + }, + ExchangeTime: 0, + } + err = s.dao.InsertCdk(cdk) + if err != nil { + return nil, err + } + } + + return nil, nil +} diff --git a/service/backend/service/cdk/createcdktype.go b/service/backend/service/cdk/createcdktype.go new file mode 100644 index 0000000..d5d3503 --- /dev/null +++ b/service/backend/service/cdk/createcdktype.go @@ -0,0 +1,51 @@ +package cdk + +import ( + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/service/backend/model/db" + "gitlab.33.cn/chat/dtalk/service/backend/model/types" + "gitlab.33.cn/utils/go-kit/convert" + "time" +) + +func (s *ServiceContent) CreateCdkTypeSvc(req *types.CreateCdkTypeReq) (res *types.CreateCdkTypeResp, err error) { + defer func() { + if err != nil { + s.log.Err(err).Interface("req", req).Msg("CreateCdkTypeSvc") + } else { + s.log.Info().Interface("req", req).Msg("CreateCdkTypeSvc") + } + }() + + cdkId, err := s.idGenRPCClient.GetID() + if err != nil { + return nil, err + } + + _, err = s.GetCdkTypeByCoinName(req.CoinName) + if err != nil && err.Error() != xerror.NewError(xerror.CdkCoinNameErr).Error() { + return nil, err + } + + cdkTypePo := &db.CdkType{ + CdkId: cdkId, + CdkName: req.CdkName, + CdkInfo: req.CdkInfo, + CoinName: req.CoinName, + ExchangeRate: req.ExchangeRate, + TimeInfo: db.TimeInfo{ + CreateTime: time.Now().UnixNano() / 1e6, + UpdateTime: time.Now().UnixNano() / 1e6, + DeleteTime: 0, + }, + } + err = s.dao.InsertCdkType(cdkTypePo) + if err != nil { + return nil, err + } + res = &types.CreateCdkTypeResp{ + CdkId: convert.ToString(cdkId), + } + + return res, nil +} diff --git a/service/backend/service/cdk/dealcdkorder.go b/service/backend/service/cdk/dealcdkorder.go new file mode 100644 index 0000000..9321b2a --- /dev/null +++ b/service/backend/service/cdk/dealcdkorder.go @@ -0,0 +1,165 @@ +package cdk + +import ( + chainTypes "github.com/33cn/chain33/types" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/service/backend/model/biz" + "gitlab.33.cn/chat/dtalk/service/backend/model/db" + "gitlab.33.cn/chat/dtalk/service/backend/model/types" + "gitlab.33.cn/utils/go-kit/convert" + "time" +) + +func (s *ServiceContent) DealCdkOrderSvc(req *types.DealCdkOrderReq) (res *types.DealCdkOrderResp, err error) { + personId := req.PersonId + orderId := convert.ToInt64(req.OrderId) + //result := req.Result + transferHash := req.TransferHash + + defer func() { + if err != nil { + s.log.Err(err).Interface("req", req).Str("personId", personId).Msg("DealCdkOrderSvc") + } else { + s.log.Info().Interface("req", req).Str("personId", personId).Msg("DealCdkOrderSvc") + } + }() + + // 根据 orderId 查询 cdks + cdks, err := s.getCdksByOrderId(orderId) + if err != nil { + return nil, err + } + + // 判断 personId 是否一致 + if cdks[0].CheckUserId(personId) == false { + return nil, xerror.NewError(xerror.CdkOrderError) + } + + // 判断 cdk 是否处于冻结状态 + if cdks[0].CheckFrozen() == false { + return nil, xerror.NewError(xerror.CdkStatusNotFrozen) + } + + // 异步处理订单 + ids := make([]int64, 0, len(cdks)) + for _, cdk := range cdks { + ids = append(ids, cdk.Id) + } + go s.dealOrder(ids, transferHash, orderId) + + // 根据 result 判断处理流程 + + //if result { + // err = s.dealSuccessCdkOrder(ids, req.TransferHash) + //} else { + // err = s.dealFailedCdkOrder(ids) + //} + //if err != nil { + // return nil, err + //} + + return &types.DealCdkOrderResp{}, nil +} + +// dealOrder 查询交易 hash 情况并处理订单 +func (s *ServiceContent) dealOrder(ids []int64, hash string, orderId int64) { + i := 0 + maxTryTimes := 20 + for i = 0; i <= maxTryTimes; i++ { + res, err := s.checkBlockTxResult(hash) + if err != nil { + time.Sleep(1 * time.Second) + continue + } + + if res.Success == true { + err = s.dealSuccessCdkOrder(ids) + if err != nil { + s.log.Error().Err(err).Int64("orderId", orderId).Str("hash", hash).Msg("dealSuccessCdkOrder err") + } + return + } else { + break + } + } + + if i > maxTryTimes { + s.log.Error().Int64("orderId", orderId).Str("hash", hash).Msg("transfer hash not exist err") + } else { + s.log.Error().Int64("orderId", orderId).Str("hash", hash).Msg("transfer hash failed") + } + + err := s.dealFailedCdkOrder(ids) + if err != nil { + s.log.Error().Err(err).Int64("orderId", orderId).Str("hash", hash).Msg("dealFailedCdkOrder err") + } + return +} + +func (s *ServiceContent) dealSuccessCdkOrder(ids []int64) error { + // + + return s.dao.UpdateCdksStatus(ids, db.CdkUsed) +} + +func (s *ServiceContent) dealFailedCdkOrder(ids []int64) error { + return s.dao.UpdateCdksStatus(ids, db.CdkUnused) +} + +func (s *ServiceContent) getCdksByOrderId(orderId int64) ([]*biz.Cdk, error) { + cdkPos, err := s.dao.GetCdksByOrderId(orderId) + if err != nil { + return nil, err + } + + if len(cdkPos) == 0 { + return nil, xerror.NewError(xerror.CdkOrderError) + } + + cdks := make([]*biz.Cdk, 0, len(cdkPos)) + for _, cdkPo := range cdkPos { + cdk := &biz.Cdk{ + Id: cdkPo.Id, + CdkId: cdkPo.CdkId, + CdkContent: cdkPo.CdkContent, + UserId: cdkPo.UserId, + CdkStatus: cdkPo.CdkStatus, + OrderId: cdkPo.OrderId, + } + cdks = append(cdks, cdk) + } + + return cdks, nil +} + +func (s *ServiceContent) checkBlockTxResult(hash string) (biz.TxResult, error) { + tx, err := s.chain33Client.GetRealTx(hash) + if err != nil { + return biz.TxResult{}, err + } + + switch tx.Receipt.GetTy() { + case chainTypes.ExecOk: + to := "" + amount := int64(0) + symbol := "" + if tx.Tx != nil { + to = tx.Tx.To + } + if len(tx.Assets) > 0 { + assert1 := tx.Assets[0] + amount = assert1.Amount + symbol = assert1.GetSymbol() + } + + return biz.TxResult{ + Success: true, + To: to, + Amount: amount, + Symbol: symbol, + }, nil + case chainTypes.ExecErr: + case chainTypes.ExecPack: + } + return biz.TxResult{Success: false}, nil +} diff --git a/service/backend/service/cdk/dealcdkorder_test.go b/service/backend/service/cdk/dealcdkorder_test.go new file mode 100644 index 0000000..fb4ef13 --- /dev/null +++ b/service/backend/service/cdk/dealcdkorder_test.go @@ -0,0 +1,94 @@ +package cdk + +import ( + "reflect" + "testing" + + chainTypes "github.com/33cn/chain33/types" + "github.com/rs/zerolog" + "gitlab.33.cn/chat/dtalk/pkg/tx" + "gitlab.33.cn/chat/dtalk/service/backend/model/biz" +) + +func TestDealFailedCdkOrder(t *testing.T) { + ids := []int64{1, 2} + err := srv.dealFailedCdkOrder(ids) + if err != nil { + t.Log(err) + } else { + t.Log("success") + } + +} + +func TestServiceContent_checkBlockTxResult(t *testing.T) { + title := "user.p.testproofv2." + var config = tx.Config{ + Grpc: tx.Grpc{ + BlockChainAddr: "172.16.101.87:8902", + }, + Chain: tx.Chain{ + FeePrikey: "", + FeeAddr: "", + Title: title, + BaseExec: title + chainTypes.NoneX, + }, + Encrypt: tx.Encrypt{ + Seed: "", + SignType: 1, + }, + } + type fields struct { + log zerolog.Logger + CdkOrderMessage chan *biz.CdkOrderMessage + CdkMaxNumber int64 + chain33Client *tx.ChainClient + } + type args struct { + hash string + } + tests := []struct { + name string + fields fields + args args + want biz.TxResult + wantErr bool + }{ + { + name: "", + fields: fields{ + CdkOrderMessage: nil, + CdkMaxNumber: 0, + chain33Client: tx.MustNewChainClient(&config), + }, + args: args{ + hash: "", + }, + want: biz.TxResult{ + Success: true, + To: "19rJWrjA8qgczqHPWLfFaSxzsSYdsepoNg", + Amount: 100000000, + Symbol: "ZJY3TUY", + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &ServiceContent{ + log: tt.fields.log, + CdkOrderMessage: tt.fields.CdkOrderMessage, + CdkMaxNumber: tt.fields.CdkMaxNumber, + chain33Client: tt.fields.chain33Client, + } + got, err := s.checkBlockTxResult(tt.args.hash) + if (err != nil) != tt.wantErr { + t.Errorf("checkBlockTxResult() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("checkBlockTxResult() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/service/backend/service/cdk/deletecdks.go b/service/backend/service/cdk/deletecdks.go new file mode 100644 index 0000000..fa62d2c --- /dev/null +++ b/service/backend/service/cdk/deletecdks.go @@ -0,0 +1,31 @@ +package cdk + +import ( + "gitlab.33.cn/chat/dtalk/service/backend/model/types" + "gitlab.33.cn/utils/go-kit/convert" + "time" +) + +func (s *ServiceContent) DeleteCdksSvc(req *types.DeleteCdksReq) (res *types.DeleteCdksResp, err error) { + defer func() { + if err != nil { + s.log.Err(err).Interface("req", req).Msg("DeleteCdksSvc") + } else { + s.log.Info().Interface("req", req).Msg("DeleteCdksSvc") + } + }() + + ids := make([]int64, len(req.Ids), len(req.Ids)) + for i, id := range req.Ids { + ids[i] = convert.ToInt64(id) + } + + err = s.dao.DeleteCdks(ids, time.Now().UnixNano()/1e6) + if err != nil { + return nil, err + } + + res = &types.DeleteCdksResp{} + + return res, nil +} diff --git a/service/backend/service/cdk/deletecdktypes.go b/service/backend/service/cdk/deletecdktypes.go new file mode 100644 index 0000000..d2c6c4c --- /dev/null +++ b/service/backend/service/cdk/deletecdktypes.go @@ -0,0 +1,35 @@ +package cdk + +import ( + "gitlab.33.cn/chat/dtalk/service/backend/model/types" + "gitlab.33.cn/utils/go-kit/convert" +) + +func (s *ServiceContent) DeleteCdkTypesSvc(req *types.DeleteCdkTypesReq) (res *types.DeleteCdkTypesResp, err error) { + defer func() { + if err != nil { + s.log.Err(err).Interface("req", req).Msg("DeleteCdkTypesSvc") + } else { + s.log.Info().Interface("req", req).Msg("DeleteCdkTypesSvc") + } + }() + + cdkIds := make([]int64, len(req.CdkIds), len(req.CdkIds)) + for i, id := range req.CdkIds { + cdkIds[i] = convert.ToInt64(id) + } + + err = s.dao.DeleteCdkTypes(cdkIds) + if err != nil { + return nil, err + } + + err = s.dao.DeleteCdksByCdkIds(cdkIds) + if err != nil { + return nil, err + } + + res = &types.DeleteCdkTypesResp{} + + return res, nil +} diff --git a/service/backend/service/cdk/exchangecdks.go b/service/backend/service/cdk/exchangecdks.go new file mode 100644 index 0000000..6ff752d --- /dev/null +++ b/service/backend/service/cdk/exchangecdks.go @@ -0,0 +1,31 @@ +package cdk + +import ( + "gitlab.33.cn/chat/dtalk/service/backend/model/db" + "gitlab.33.cn/chat/dtalk/service/backend/model/types" + "gitlab.33.cn/utils/go-kit/convert" +) + +func (s *ServiceContent) ExchangeCdksSvc(req *types.ExchangeCdksReq) (res *types.ExchangeCdksResp, err error) { + defer func() { + if err != nil { + s.log.Err(err).Interface("req", req).Msg("ExchangeCdksSvc") + } else { + s.log.Info().Interface("req", req).Msg("ExchangeCdksSvc") + } + }() + + ids := make([]int64, len(req.Ids), len(req.Ids)) + for i, id := range req.Ids { + ids[i] = convert.ToInt64(id) + } + + err = s.dao.UpdateCdksStatus(ids, db.CdkExchange) + if err != nil { + return nil, err + } + + res = &types.ExchangeCdksResp{} + + return res, nil +} diff --git a/service/backend/service/cdk/getcdks.go b/service/backend/service/cdk/getcdks.go new file mode 100644 index 0000000..1b01065 --- /dev/null +++ b/service/backend/service/cdk/getcdks.go @@ -0,0 +1,34 @@ +package cdk + +import ( + "gitlab.33.cn/chat/dtalk/service/backend/model/types" + "gitlab.33.cn/utils/go-kit/convert" +) + +func (s *ServiceContent) GetCdksSvc(req *types.GetCdksReq) (res *types.GetCdksResp, err error) { + defer func() { + if err != nil { + s.log.Err(err).Interface("req", req).Msg("GetCdksSvc") + } else { + s.log.Info().Interface("req", req).Msg("GetCdksSvc") + } + }() + + cdks, totalElements, totalPages, err := s.GetCdks(convert.ToInt64(req.CdkId), req.CdkContent, req.Page, req.PageSize) + if err != nil { + return nil, err + } + + typesCdks := make([]*types.Cdk, len(cdks), len(cdks)) + for i, cdk := range cdks { + typesCdk := cdk.ToTypes() + typesCdks[i] = typesCdk + } + res = &types.GetCdksResp{ + TotalElements: totalElements, + TotalPages: totalPages, + Cdks: typesCdks, + } + + return res, nil +} diff --git a/service/backend/service/cdk/getcdksbyuserid.go b/service/backend/service/cdk/getcdksbyuserid.go new file mode 100644 index 0000000..61edfe0 --- /dev/null +++ b/service/backend/service/cdk/getcdksbyuserid.go @@ -0,0 +1,70 @@ +package cdk + +import ( + "gitlab.33.cn/chat/dtalk/service/backend/model/biz" + "gitlab.33.cn/chat/dtalk/service/backend/model/types" +) + +func (s *ServiceContent) GetCdksByUserIdSvc(req *types.GetCdksByUserIdReq) (res *types.GetCdksByUserIdResp, err error) { + personId := req.PersonId + page := req.Page + pageSize := req.PageSize + + defer func() { + if err != nil { + s.log.Error().Err(err).Str("personId", personId).Msg("GetCdksByUserIdSvc") + } + }() + + cdks, totalElements, totalPages, err := s.GetCdksByUserId(personId, page, pageSize) + if err != nil { + return nil, err + } + + cdkVos := make([]*types.Cdk, 0, len(cdks)) + cdkNameMap := make(map[int64]string, 0) + for _, cdk := range cdks { + tcdk := cdk.ToTypes() + cdkName, ok := cdkNameMap[cdk.CdkId] + if !ok { + cdkType, err := s.GetCdkTypeByCdkId(cdk.CdkId) + if err != nil { + continue + } + cdkName = cdkType.CdkName + cdkNameMap[cdk.CdkId] = cdkName + } + tcdk.CdkName = cdkName + cdkVos = append(cdkVos, tcdk) + } + + return &types.GetCdksByUserIdResp{ + TotalElements: totalElements, + TotalPages: totalPages, + Cdks: cdkVos, + }, nil +} + +func (s *ServiceContent) GetCdksByUserId(userId string, page, pageSize int64) ([]*biz.Cdk, int64, int64, error) { + cdkPos, totalElements, totalPages, err := s.dao.GetCdksWithUserId(userId, page, pageSize) + if err != nil { + return nil, 0, 0, err + } + + cdks := make([]*biz.Cdk, 0, len(cdkPos)) + for _, cdkPo := range cdkPos { + cdk := &biz.Cdk{ + Id: cdkPo.Id, + CdkId: cdkPo.CdkId, + CdkContent: cdkPo.CdkContent, + UserId: cdkPo.UserId, + CdkStatus: cdkPo.CdkStatus, + OrderId: cdkPo.OrderId, + CreateTime: cdkPo.CreateTime, + ExchangeTime: cdkPo.ExchangeTime, + } + cdks = append(cdks, cdk) + } + + return cdks, totalElements, totalPages, nil +} diff --git a/service/backend/service/cdk/getcdktypebycoinname.go b/service/backend/service/cdk/getcdktypebycoinname.go new file mode 100644 index 0000000..7838ccf --- /dev/null +++ b/service/backend/service/cdk/getcdktypebycoinname.go @@ -0,0 +1,22 @@ +package cdk + +import "gitlab.33.cn/chat/dtalk/service/backend/model/types" + +func (s *ServiceContent) GetCdkTypeByCoinNameSvc(req *types.GetCdkTypeByCoinNameReq) (res *types.GetCdkTypeByCoinNameResp, err error) { + coinName := req.CoinName + + defer func() { + if err != nil { + s.log.Error().Err(err).Str("coinName", coinName).Msg("GetCdkTypeByCoinNameSvc") + } + }() + + cdkType, err := s.GetCdkTypeByCoinName(coinName) + if err != nil { + return nil, err + } + + return &types.GetCdkTypeByCoinNameResp{ + CdkType: cdkType.ToTypes(), + }, nil +} diff --git a/service/backend/service/cdk/getcdktypes.go b/service/backend/service/cdk/getcdktypes.go new file mode 100644 index 0000000..9562c7c --- /dev/null +++ b/service/backend/service/cdk/getcdktypes.go @@ -0,0 +1,31 @@ +package cdk + +import "gitlab.33.cn/chat/dtalk/service/backend/model/types" + +func (s *ServiceContent) GetCdkTypesSvc(req *types.GetCdkTypesReq) (res *types.GetCdkTypesResp, err error) { + defer func() { + if err != nil { + s.log.Err(err).Interface("req", req).Msg("GetCdkTypesSvc") + } else { + s.log.Info().Interface("req", req).Msg("GetCdkTypesSvc") + } + }() + + cdkTypes, totalElements, totalPages, err := s.GetCdkTypes(req.CoinName, req.Page, req.PageSize) + if err != nil { + return nil, err + } + + typesCdks := make([]*types.CdkType, len(cdkTypes), len(cdkTypes)) + for i, cdkType := range cdkTypes { + typesCdkType := cdkType.ToTypes() + typesCdks[i] = typesCdkType + } + res = &types.GetCdkTypesResp{ + TotalElements: totalElements, + TotalPages: totalPages, + CdkTypes: typesCdks, + } + + return res, nil +} diff --git a/service/backend/service/cdk/svc.go b/service/backend/service/cdk/svc.go new file mode 100644 index 0000000..2e69ed6 --- /dev/null +++ b/service/backend/service/cdk/svc.go @@ -0,0 +1,59 @@ +package cdk + +import ( + chainTypes "github.com/33cn/chain33/types" + "github.com/rs/zerolog" + "gitlab.33.cn/chat/dtalk/pkg/logger" + "gitlab.33.cn/chat/dtalk/pkg/tx" + "gitlab.33.cn/chat/dtalk/service/backend/config" + "gitlab.33.cn/chat/dtalk/service/backend/dao" + "gitlab.33.cn/chat/dtalk/service/backend/model/biz" + idgen "gitlab.33.cn/chat/dtalk/service/generator/api" +) + +type ServiceContent struct { + log zerolog.Logger + dao *dao.Dao + idGenRPCClient *idgen.Client + // cdk订单处理 channal + CdkOrderMessage chan *biz.CdkOrderMessage + CdkMaxNumber int64 + chain33Client *tx.ChainClient +} + +func NewServiceContent(env string, dao *dao.Dao, idgen *idgen.Client, cdkMaxNumber int64, chainCliConfig config.Chain33Client) *ServiceContent { + s := &ServiceContent{ + log: logger.New(env, "cdkOrder"), + dao: dao, + idGenRPCClient: idgen, + CdkOrderMessage: make(chan *biz.CdkOrderMessage, 100), + CdkMaxNumber: cdkMaxNumber, + chain33Client: initChain33Client(chainCliConfig), + } + go s.ListenOrder() + + go s.CleanFrozenOrder() + + return s +} + +func initChain33Client(chainCliConfig config.Chain33Client) *tx.ChainClient { + title := chainCliConfig.Title + cfg := tx.Config{ + Grpc: tx.Grpc{ + BlockChainAddr: chainCliConfig.BlockChainAddr, + }, + Chain: tx.Chain{ + FeePrikey: "", + FeeAddr: "", + Title: title, + BaseExec: title + chainTypes.NoneX, + }, + Encrypt: tx.Encrypt{ + Seed: "", + SignType: 1, + }, + } + + return tx.MustNewChainClient(&cfg) +} diff --git a/service/backend/service/cdk/svc_test.go b/service/backend/service/cdk/svc_test.go new file mode 100644 index 0000000..2a643cb --- /dev/null +++ b/service/backend/service/cdk/svc_test.go @@ -0,0 +1,26 @@ +package cdk + +import ( + "gitlab.33.cn/chat/dtalk/service/backend/config" + "gitlab.33.cn/chat/dtalk/service/backend/dao" + idgen "gitlab.33.cn/chat/dtalk/service/generator/api" + "os" + "testing" + "time" +) + +var ( + cfg *config.Config + srv *ServiceContent + Dao *dao.Dao + idGenRPCClient *idgen.Client +) + +func TestMain(m *testing.M) { + cfg = config.Default() + Dao = dao.New(cfg) + idGenRPCClient = idgen.New(cfg.IdGenRPCClient.RegAddrs, cfg.IdGenRPCClient.Schema, cfg.IdGenRPCClient.SrvName, time.Duration(cfg.IdGenRPCClient.Dial)) + srv = NewServiceContent(cfg.Env, Dao, idGenRPCClient) + + os.Exit(m.Run()) +} diff --git a/service/backend/service/cdk/updatecdktype.go b/service/backend/service/cdk/updatecdktype.go new file mode 100644 index 0000000..84c53e0 --- /dev/null +++ b/service/backend/service/cdk/updatecdktype.go @@ -0,0 +1,24 @@ +package cdk + +import ( + "gitlab.33.cn/chat/dtalk/service/backend/model/types" + "gitlab.33.cn/utils/go-kit/convert" +) + +func (s *ServiceContent) UpdateCdkTypeSvc(req *types.UpdateCdkTypeReq) (res *types.UpdateCdkTypeResp, err error) { + defer func() { + if err != nil { + s.log.Err(err).Interface("req", req).Msg("UpdateCdkTypeSvc") + } else { + s.log.Info().Interface("req", req).Msg("UpdateCdkTypeSvc") + } + }() + + cdkId := convert.ToInt64(req.CdkId) + err = s.dao.UpdateCdkType(cdkId, req.CdkName, req.CoinName, req.ExchangeRate) + if err != nil { + return nil, err + } + + return &types.UpdateCdkTypeResp{}, nil +} diff --git a/service/backend/service/service.go b/service/backend/service/service.go new file mode 100644 index 0000000..b105fb3 --- /dev/null +++ b/service/backend/service/service.go @@ -0,0 +1,44 @@ +package service + +import ( + "github.com/inconshreveable/log15" + "gitlab.33.cn/chat/dtalk/service/backend/config" + "gitlab.33.cn/chat/dtalk/service/backend/dao" + "gitlab.33.cn/chat/dtalk/service/backend/service/cdk" + idgen "gitlab.33.cn/chat/dtalk/service/generator/api" + "time" +) + +type Service struct { + log log15.Logger + cfg *config.Config + dao *dao.Dao + Platform string + idGenRPCClient *idgen.Client + + CdkService *cdk.ServiceContent +} + +func New(cfg *config.Config) *Service { + s := &Service{ + log: log15.New("module", "backend/svc"), + cfg: cfg, + dao: dao.New(cfg), + Platform: cfg.Platform, + idGenRPCClient: idgen.New(cfg.IdGenRPCClient.RegAddrs, cfg.IdGenRPCClient.Schema, cfg.IdGenRPCClient.SrvName, time.Duration(cfg.IdGenRPCClient.Dial)), + } + + if cfg.CdkMod { + s.CdkService = cdk.NewServiceContent(cfg.Env, s.dao, s.idGenRPCClient, cfg.CdkMaxNumber, cfg.Chain33Client) + } + + return s +} + +func (s *Service) Ping() error { + return nil +} + +func (s Service) Config() *config.Config { + return s.cfg +} diff --git a/service/backend/service/version.go b/service/backend/service/version.go new file mode 100644 index 0000000..58baaaf --- /dev/null +++ b/service/backend/service/version.go @@ -0,0 +1,192 @@ +package service + +import ( + "github.com/dgrijalva/jwt-go" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/service/backend/config" + "gitlab.33.cn/chat/dtalk/service/backend/model" + "time" +) + +func (s *Service) CreateVersion(request *model.VersionCreateRequest) (res *model.VersionCreateResponse, err error) { + defer func() { + if err != nil { + s.log.Error("CreateVersion", "err", err, "req", request) + } else { + s.log.Info("CreateVersion", "req", request) + } + }() + + versionForm := model.VersionForm{ + Platform: request.Platform, + DeviceType: request.DeviceType, + VersionName: request.VersionName, + VersionCode: request.VersionCode, + Url: request.Url, + Size: request.Size, + Md5: request.Md5, + Description: request.Description, + Force: request.Force, + OpeUser: request.OpeUser, + CreateTime: time.Now().UnixNano() / 1e6, + UpdateTime: time.Now().UnixNano() / 1e6, + } + _, id, err := s.dao.InsertVersion(&versionForm) + if err != nil { + return nil, err + } + versionForm.Id = id + + return &model.VersionCreateResponse{ + Version: versionForm, + }, err +} + +func (s *Service) UpdateVersion(request *model.VersionUpdateRequest) (res *model.VersionUpdateResponse, err error) { + defer func() { + if err != nil { + s.log.Error("UpdateVersion", "err", err, "req", request) + } else { + s.log.Info("UpdateVersion", "req", request) + } + }() + versionForm := model.VersionForm{ + Id: request.Id, + VersionName: request.VersionName, + VersionCode: request.VersionCode, + Url: request.Url, + Description: request.Description, + UpdateTime: time.Now().UnixNano() / 1e6, + Force: request.Force, + OpeUser: request.OpeUser, + Md5: request.Md5, + Size: request.Size, + } + + result, err := s.dao.UpdateVersion(&versionForm) + if err != nil { + return nil, err + } + response := &model.VersionUpdateResponse{ + Version: *result, + } + return response, err +} + +func (s *Service) ChangeVersionStatus(request *model.VersionChangeStatusRequest) (err error) { + defer func() { + if err != nil { + s.log.Error("ChangeVersionStatus", "err", err, "req", request) + } else { + s.log.Info("ChangeVersionStatus", "req", request) + } + }() + versionForm := model.VersionForm{ + Id: request.Id, + UpdateTime: time.Now().UnixNano() / 1e6, + OpeUser: request.OpeUser, + } + err = s.dao.ChangeVersionStatus(&versionForm) + return err +} + +func (s *Service) GetVersionList(request *model.GetVersionListRequest) (res *model.GetVersionListResponse, err error) { + defer func() { + if err != nil { + s.log.Error("GetVersionList", "err", err, "req", request) + } else { + s.log.Info("GetVersionList", "req", request) + } + }() + versionForm := model.VersionForm{ + Platform: request.Platform, + DeviceType: request.DeviceType, + } + versionList, totalElements, totalPages, err := s.dao.GetVersionList(&versionForm, request.Page, model.Size) + if err != nil { + return nil, err + } + response := &model.GetVersionListResponse{ + TotalElements: totalElements, + TotalPages: totalPages, + VersionList: *versionList, + } + return response, err +} + +func (s *Service) CheckAndUpdateVersion(request *model.VersionCheckAndUpdateRequest) (res *model.VersionCheckAndUpdateResponse, err error) { + defer func() { + if err != nil { + s.log.Error("CheckAndUpdateVersion", "err", err, "req", request) + } else { + s.log.Info("CheckAndUpdateVersion", "req", request) + } + }() + versionForm := model.VersionForm{ + VersionCode: request.VersionCode, + DeviceType: request.DeviceType, + Platform: s.Platform, + } + result, err := s.dao.CheckAndUpdateVersion(&versionForm) + if err != nil { + return nil, err + } + response := &model.VersionCheckAndUpdateResponse{} + + response.Id = result.Id + response.Platform = result.Platform + response.Status = result.Status + response.DeviceType = result.DeviceType + response.VersionName = result.VersionName + response.VersionCode = result.VersionCode + response.Url = result.Url + response.Force = result.Force + response.Description = result.Description + response.OpeUser = result.OpeUser + response.Md5 = result.Md5 + response.Size = result.Size + response.UpdateTime = result.UpdateTime + response.CreateTime = result.CreateTime + + return response, err +} + +func (s *Service) GetToken(request *model.GetTokenRequest) (res *model.GetTokenResponse, err error) { + defer func() { + if err != nil { + s.log.Error("GetToken", "err", err, "req", request) + } else { + s.log.Info("GetToken", "req", request) + } + }() + userInfo := model.UserInfo{ + UserName: request.UserName, + Password: request.Password, + } + if userInfo.UserName != config.Conf.Release.UserName || userInfo.Password != config.Conf.Release.Password { + return nil, xerror.NewError(xerror.ParamsError).SetExtMessage("用户名或密码错误") + } + token, err := s.createToken(userInfo.UserName) + if err != nil { + return nil, err + } + + return &model.GetTokenResponse{ + UserInfo: model.UserInfoResponse{ + UserName: userInfo.UserName, + Token: token, + }, + }, nil +} + +func (s *Service) createToken(username string) (string, error) { + c := model.Claims{ + Username: username, + StandardClaims: jwt.StandardClaims{ + ExpiresAt: time.Now().Add(config.Conf.Release.TokenExpireDuration).Unix(), + Issuer: config.Conf.Release.Issuer, + }, + } + token := jwt.NewWithClaims(jwt.SigningMethodHS256, c) + return token.SignedString([]byte(config.Conf.Release.Key)) +} diff --git a/service/backend/version.go b/service/backend/version.go new file mode 100644 index 0000000..e68c209 --- /dev/null +++ b/service/backend/version.go @@ -0,0 +1,16 @@ +package version + +//var ( +// // The full version string +// Version = "0.2.3" +// +// // GitCommit is set with --ldflags "-X main.gitCommit=$(git rev-parse --short=8 HEAD)" +// GitCommit string +//) +// +//func GetVersion() string { +// if GitCommit != "" { +// return Version + "-" + GitCommit +// } +// return Version +//} diff --git a/service/backup/CHANGELOG.md b/service/backup/CHANGELOG.md new file mode 100644 index 0000000..403cc18 --- /dev/null +++ b/service/backup/CHANGELOG.md @@ -0,0 +1,52 @@ +版本号`major.minor.patch`具体规则如下: +- major:主版本号,如有重大版本重构则该字段递增,通常各主版本间接口不兼容。 +- minor:次版本号,各次版本号间接口保持兼容,如有接口新增或优化则该字段递增。 +- patch:补丁号,如有功能改善或缺陷修复则该字段递增。 + +## version 1.1.1 2021_12_23 + +**数据库** +- 新增 `dtalk_addr_move` 表 + +**Feature** +- 增加登记地址接口 + +## version 1.1.0 2021_12_09 +**Feature** +- 增加 v2 接口 +- backup服务手机邮箱验证分为三种模式bind、quick、export + +## version 1.0.15 2021.10.22 + +**配置文件新增** +```toml +[[Whitelist]] +Account="" +Code="" +Enable=true +``` + +**Feature** +- 增加短信邮箱验证白名单 @v1.0.15 2021.10.22 +- 更新 etcdv3.5.0 v1.0.18 2021.11.03 + +## version 1.0.9 @2021.8.3 + +**Feature** + +- 支持通过手机或邮箱获得地址 +- 优化验证码发送错误提示 @v1.0.9 2021.8.3 +- 修复 grpc nil panic v1.0.12 + + + + +## example x.x.x @yy.mm.dd + +**Feature** + +**Bug Fixes** + +**Improvement** + +**Breaking Change** diff --git a/service/backup/Makefile b/service/backup/Makefile new file mode 100644 index 0000000..aa460ff --- /dev/null +++ b/service/backup/Makefile @@ -0,0 +1,44 @@ +# golang1.9 or latest +# 1. make help +# 2. make dep +# 3. make build +# ... + +VERSION := $(shell echo $(shell cat cmd/main.go | grep "projectVersion =" | cut -d '=' -f2)) +APP_NAME := backup +BUILD_DIR := build +APP := ${BUILD_DIR}/${APP_NAME}_v${VERSION} +PKG_NAME := ${APP_NAME}_v${VERSION} +PKG := ${PKG_NAME}.tar.gz + +main_path = "main" +go_version = $(shell go version | awk '{ print $3 }') +build_time = $(shell date "+%Y-%m-%d %H:%M:%S %Z") +git_commit = $(shell git rev-parse --short=10 HEAD) +flags := -ldflags "-X '${main_path}.goVersion=${go_version}' \ +-X '${main_path}.buildTime=${build_time}' \ +-X '${main_path}.gitCommit=${git_commit}' \ +-X 'google.golang.org/protobuf/reflect/protoregistry.conflictPolicy=warn'" + +.PHONY: clean build pkg + +clean: ## Remove previous build + @rm -rf ${BUILD_DIR} + @go clean + +build: #checkgofmt ## Build the binary file + swag init -g server/http/http.go + GOOS=linux GOARCH=amd64 GO111MODULE=on GOPROXY=https://goproxy.cn,direct GOSUMDB="sum.golang.google.cn" go build -v $(flags) -o $(APP) cmd/main.go + +pkg: build ## Package + mkdir -p ${PKG_NAME}/bin + mkdir -p ${PKG_NAME}/etc + cp ${APP} ${PKG_NAME}/bin/ + cp etc/* ${PKG_NAME}/etc/ + tar zvcf ${PKG} ${PKG_NAME} + rm -rf ${PKG_NAME} + +REMOTE_BIN_PATH := /opt/dtalk/srv/app/bin +upload: build + rsync -r $(APP) 107:$(REMOTE_BIN_PATH) + ssh 107 "cd $(REMOTE_BIN_PATH) && chmod 777 $(PKG_NAME) && ln -sf $(PKG_NAME) $(APP_NAME) && supervisorctl restart $(APP_NAME)" \ No newline at end of file diff --git a/service/backup/api/api.pb.go b/service/backup/api/api.pb.go new file mode 100644 index 0000000..672a85e --- /dev/null +++ b/service/backup/api/api.pb.go @@ -0,0 +1,349 @@ +// protoc -I=. -I=$GOPATH/src --go_out=plugins=grpc:. *.proto + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc v3.19.1 +// source: api.proto + +package backup + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type QueryType int32 + +const ( + QueryType_Phone QueryType = 0 + QueryType_Email QueryType = 1 + QueryType_Address QueryType = 2 +) + +// Enum value maps for QueryType. +var ( + QueryType_name = map[int32]string{ + 0: "Phone", + 1: "Email", + 2: "Address", + } + QueryType_value = map[string]int32{ + "Phone": 0, + "Email": 1, + "Address": 2, + } +) + +func (x QueryType) Enum() *QueryType { + p := new(QueryType) + *p = x + return p +} + +func (x QueryType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (QueryType) Descriptor() protoreflect.EnumDescriptor { + return file_api_proto_enumTypes[0].Descriptor() +} + +func (QueryType) Type() protoreflect.EnumType { + return &file_api_proto_enumTypes[0] +} + +func (x QueryType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use QueryType.Descriptor instead. +func (QueryType) EnumDescriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{0} +} + +type RetrieveReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Type QueryType `protobuf:"varint,1,opt,name=type,proto3,enum=dtalk.backup.QueryType" json:"type,omitempty"` + Val string `protobuf:"bytes,2,opt,name=val,proto3" json:"val,omitempty"` +} + +func (x *RetrieveReq) Reset() { + *x = RetrieveReq{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RetrieveReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RetrieveReq) ProtoMessage() {} + +func (x *RetrieveReq) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RetrieveReq.ProtoReflect.Descriptor instead. +func (*RetrieveReq) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{0} +} + +func (x *RetrieveReq) GetType() QueryType { + if x != nil { + return x.Type + } + return QueryType_Phone +} + +func (x *RetrieveReq) GetVal() string { + if x != nil { + return x.Val + } + return "" +} + +type RetrieveReply struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Address string `protobuf:"bytes,1,opt,name=Address,proto3" json:"Address,omitempty"` + Area string `protobuf:"bytes,2,opt,name=Area,proto3" json:"Area,omitempty"` + Phone string `protobuf:"bytes,3,opt,name=Phone,proto3" json:"Phone,omitempty"` + Email string `protobuf:"bytes,4,opt,name=Email,proto3" json:"Email,omitempty"` + Mnemonic string `protobuf:"bytes,5,opt,name=Mnemonic,proto3" json:"Mnemonic,omitempty"` + PrivateKey string `protobuf:"bytes,6,opt,name=PrivateKey,proto3" json:"PrivateKey,omitempty"` + UpdateTime int64 `protobuf:"varint,7,opt,name=UpdateTime,proto3" json:"UpdateTime,omitempty"` + CreateTime int64 `protobuf:"varint,8,opt,name=CreateTime,proto3" json:"CreateTime,omitempty"` +} + +func (x *RetrieveReply) Reset() { + *x = RetrieveReply{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RetrieveReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RetrieveReply) ProtoMessage() {} + +func (x *RetrieveReply) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RetrieveReply.ProtoReflect.Descriptor instead. +func (*RetrieveReply) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{1} +} + +func (x *RetrieveReply) GetAddress() string { + if x != nil { + return x.Address + } + return "" +} + +func (x *RetrieveReply) GetArea() string { + if x != nil { + return x.Area + } + return "" +} + +func (x *RetrieveReply) GetPhone() string { + if x != nil { + return x.Phone + } + return "" +} + +func (x *RetrieveReply) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +func (x *RetrieveReply) GetMnemonic() string { + if x != nil { + return x.Mnemonic + } + return "" +} + +func (x *RetrieveReply) GetPrivateKey() string { + if x != nil { + return x.PrivateKey + } + return "" +} + +func (x *RetrieveReply) GetUpdateTime() int64 { + if x != nil { + return x.UpdateTime + } + return 0 +} + +func (x *RetrieveReply) GetCreateTime() int64 { + if x != nil { + return x.CreateTime + } + return 0 +} + +var File_api_proto protoreflect.FileDescriptor + +var file_api_proto_rawDesc = []byte{ + 0x0a, 0x09, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0c, 0x64, 0x74, 0x61, + 0x6c, 0x6b, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x22, 0x4c, 0x0a, 0x0b, 0x52, 0x65, 0x74, + 0x72, 0x69, 0x65, 0x76, 0x65, 0x52, 0x65, 0x71, 0x12, 0x2b, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x64, 0x74, 0x61, 0x6c, 0x6b, 0x2e, 0x62, + 0x61, 0x63, 0x6b, 0x75, 0x70, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x54, 0x79, 0x70, 0x65, 0x52, + 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x76, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x76, 0x61, 0x6c, 0x22, 0xe5, 0x01, 0x0a, 0x0d, 0x52, 0x65, 0x74, 0x72, + 0x69, 0x65, 0x76, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x41, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x41, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x41, 0x72, 0x65, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x41, 0x72, 0x65, 0x61, 0x12, 0x14, 0x0a, 0x05, 0x50, 0x68, 0x6f, 0x6e, 0x65, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x50, 0x68, 0x6f, 0x6e, 0x65, 0x12, 0x14, 0x0a, + 0x05, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x45, 0x6d, + 0x61, 0x69, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x4d, 0x6e, 0x65, 0x6d, 0x6f, 0x6e, 0x69, 0x63, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x4d, 0x6e, 0x65, 0x6d, 0x6f, 0x6e, 0x69, 0x63, 0x12, + 0x1e, 0x0a, 0x0a, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0a, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, + 0x1e, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, + 0x1e, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x2a, + 0x2e, 0x0a, 0x09, 0x51, 0x75, 0x65, 0x72, 0x79, 0x54, 0x79, 0x70, 0x65, 0x12, 0x09, 0x0a, 0x05, + 0x50, 0x68, 0x6f, 0x6e, 0x65, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x6d, 0x61, 0x69, 0x6c, + 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x10, 0x02, 0x32, + 0x4c, 0x0a, 0x06, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, 0x42, 0x0a, 0x08, 0x52, 0x65, 0x74, + 0x72, 0x69, 0x65, 0x76, 0x65, 0x12, 0x19, 0x2e, 0x64, 0x74, 0x61, 0x6c, 0x6b, 0x2e, 0x62, 0x61, + 0x63, 0x6b, 0x75, 0x70, 0x2e, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x52, 0x65, 0x71, + 0x1a, 0x1b, 0x2e, 0x64, 0x74, 0x61, 0x6c, 0x6b, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x2e, + 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x42, 0x28, 0x5a, + 0x26, 0x67, 0x69, 0x74, 0x6c, 0x61, 0x62, 0x2e, 0x33, 0x33, 0x2e, 0x63, 0x6e, 0x2f, 0x63, 0x68, + 0x61, 0x74, 0x2f, 0x64, 0x74, 0x61, 0x6c, 0x6b, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x2f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_api_proto_rawDescOnce sync.Once + file_api_proto_rawDescData = file_api_proto_rawDesc +) + +func file_api_proto_rawDescGZIP() []byte { + file_api_proto_rawDescOnce.Do(func() { + file_api_proto_rawDescData = protoimpl.X.CompressGZIP(file_api_proto_rawDescData) + }) + return file_api_proto_rawDescData +} + +var file_api_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_api_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_api_proto_goTypes = []interface{}{ + (QueryType)(0), // 0: dtalk.backup.QueryType + (*RetrieveReq)(nil), // 1: dtalk.backup.RetrieveReq + (*RetrieveReply)(nil), // 2: dtalk.backup.RetrieveReply +} +var file_api_proto_depIdxs = []int32{ + 0, // 0: dtalk.backup.RetrieveReq.type:type_name -> dtalk.backup.QueryType + 1, // 1: dtalk.backup.Backup.Retrieve:input_type -> dtalk.backup.RetrieveReq + 2, // 2: dtalk.backup.Backup.Retrieve:output_type -> dtalk.backup.RetrieveReply + 2, // [2:3] is the sub-list for method output_type + 1, // [1:2] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_api_proto_init() } +func file_api_proto_init() { + if File_api_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_api_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RetrieveReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RetrieveReply); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_api_proto_rawDesc, + NumEnums: 1, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_api_proto_goTypes, + DependencyIndexes: file_api_proto_depIdxs, + EnumInfos: file_api_proto_enumTypes, + MessageInfos: file_api_proto_msgTypes, + }.Build() + File_api_proto = out.File + file_api_proto_rawDesc = nil + file_api_proto_goTypes = nil + file_api_proto_depIdxs = nil +} diff --git a/service/backup/api/api.proto b/service/backup/api/api.proto new file mode 100644 index 0000000..326b904 --- /dev/null +++ b/service/backup/api/api.proto @@ -0,0 +1,31 @@ +// protoc -I=. -I=$GOPATH/src --go_out=plugins=grpc:. *.proto +syntax = "proto3"; + +package dtalk.backup; +option go_package = "gitlab.33.cn/chat/dtalk/service/backup"; + +enum QueryType{ + Phone = 0; + Email = 1; + Address = 2; +} + +message RetrieveReq { + QueryType type = 1; + string val = 2; +} + +message RetrieveReply { + string Address = 1; + string Area = 2; + string Phone = 3; + string Email = 4; + string Mnemonic = 5; + string PrivateKey = 6; + int64 UpdateTime = 7; + int64 CreateTime = 8; +} + +service Backup { + rpc Retrieve(RetrieveReq) returns (RetrieveReply); +} diff --git a/service/backup/api/api_grpc.pb.go b/service/backup/api/api_grpc.pb.go new file mode 100644 index 0000000..e400c12 --- /dev/null +++ b/service/backup/api/api_grpc.pb.go @@ -0,0 +1,101 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. + +package backup + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// BackupClient is the client API for Backup service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type BackupClient interface { + Retrieve(ctx context.Context, in *RetrieveReq, opts ...grpc.CallOption) (*RetrieveReply, error) +} + +type backupClient struct { + cc grpc.ClientConnInterface +} + +func NewBackupClient(cc grpc.ClientConnInterface) BackupClient { + return &backupClient{cc} +} + +func (c *backupClient) Retrieve(ctx context.Context, in *RetrieveReq, opts ...grpc.CallOption) (*RetrieveReply, error) { + out := new(RetrieveReply) + err := c.cc.Invoke(ctx, "/dtalk.backup.Backup/Retrieve", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// BackupServer is the server API for Backup service. +// All implementations must embed UnimplementedBackupServer +// for forward compatibility +type BackupServer interface { + Retrieve(context.Context, *RetrieveReq) (*RetrieveReply, error) + mustEmbedUnimplementedBackupServer() +} + +// UnimplementedBackupServer must be embedded to have forward compatible implementations. +type UnimplementedBackupServer struct { +} + +func (UnimplementedBackupServer) Retrieve(context.Context, *RetrieveReq) (*RetrieveReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method Retrieve not implemented") +} +func (UnimplementedBackupServer) mustEmbedUnimplementedBackupServer() {} + +// UnsafeBackupServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to BackupServer will +// result in compilation errors. +type UnsafeBackupServer interface { + mustEmbedUnimplementedBackupServer() +} + +func RegisterBackupServer(s grpc.ServiceRegistrar, srv BackupServer) { + s.RegisterService(&Backup_ServiceDesc, srv) +} + +func _Backup_Retrieve_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RetrieveReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(BackupServer).Retrieve(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.backup.Backup/Retrieve", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BackupServer).Retrieve(ctx, req.(*RetrieveReq)) + } + return interceptor(ctx, in, info, handler) +} + +// Backup_ServiceDesc is the grpc.ServiceDesc for Backup service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Backup_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "dtalk.backup.Backup", + HandlerType: (*BackupServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Retrieve", + Handler: _Backup_Retrieve_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "api.proto", +} diff --git a/service/backup/api/client.go b/service/backup/api/client.go new file mode 100644 index 0000000..0848454 --- /dev/null +++ b/service/backup/api/client.go @@ -0,0 +1,41 @@ +package backup + +import ( + "context" + "fmt" + "gitlab.33.cn/chat/dtalk/pkg/naming" + "gitlab.33.cn/chat/dtalk/pkg/net/grpc" + "google.golang.org/grpc/resolver" + "time" +) + +// AppID unique app id for service discovery +//const AppID = "identify.service.pusher" + +type Client struct { + client BackupClient +} + +func New(etcdAddr, schema, srvName string, dial time.Duration) *Client { + rb := naming.NewResolver(etcdAddr, schema) + resolver.Register(rb) + + addr := fmt.Sprintf("%s:///%s", schema, srvName) // "schema://[authority]/service" + fmt.Println("backup rpc client call addr:", addr) + + conn, err := grpc.NewGRPCConn(addr, dial) + if err != nil { + panic(err) + } + return &Client{ + client: NewBackupClient(conn), + } +} + +func (c *Client) Retrieve(ctx context.Context, tp QueryType, val string) (*RetrieveReply, error) { + reply, err := c.client.Retrieve(ctx, &RetrieveReq{ + Type: tp, + Val: val, + }) + return reply, err +} diff --git a/service/backup/api/create.sh b/service/backup/api/create.sh new file mode 100644 index 0000000..0e5dbf9 --- /dev/null +++ b/service/backup/api/create.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +protoc -I. -I$GOPATH/src \ + --go_out=. --go_opt=paths=source_relative \ + --go-grpc_out=. --go-grpc_opt=paths=source_relative \ + *.proto \ No newline at end of file diff --git a/service/backup/cmd/main.go b/service/backup/cmd/main.go new file mode 100644 index 0000000..c70491d --- /dev/null +++ b/service/backup/cmd/main.go @@ -0,0 +1,116 @@ +package main + +import ( + "context" + "flag" + "fmt" + "net" + "os" + "os/signal" + "syscall" + "time" + + "github.com/Terry-Mao/goim/pkg/ip" + "github.com/inconshreveable/log15" + "gitlab.33.cn/chat/dtalk/pkg/naming" + "gitlab.33.cn/chat/dtalk/service/backup/config" + "gitlab.33.cn/chat/dtalk/service/backup/server/grpc" + "gitlab.33.cn/chat/dtalk/service/backup/server/http" + "gitlab.33.cn/chat/dtalk/service/backup/service" +) + +const srvName = "backup" + +var log = log15.New("cmd", srvName) + +var ( + // projectVersion 项目版本 + projectVersion = "1.1.1" + // goVersion go版本 + goVersion = "" + // gitCommit git提交commit id + gitCommit = "" + // buildTime 编译时间 + buildTime = "" + + isShowVersion = flag.Bool("v", false, "show project version") +) + +// showVersion 显示项目版本信息 +func showVersion(isShow bool) { + if isShow { + fmt.Printf("Project: %s\n", srvName) + fmt.Printf(" Version: %s\n", projectVersion) + fmt.Printf(" Go Version: %s\n", goVersion) + fmt.Printf(" Git Commit: %s\n", gitCommit) + fmt.Printf(" Build Time: %s\n", buildTime) + os.Exit(0) + } +} + +// @Title 聊天单模块集成测试 +// @Version 0.1 +// @Description +// @SecurityDefinitions.ApiKey ApiKeyAuth +// @In header +// @Name Authorization +// @BasePath / +func main() { + flag.Parse() + showVersion(*isShowVersion) + + if err := config.Init(); err != nil { + panic(err) + } + log.Info("config info:", + "Env", config.Conf.Env, + "SMS", config.Conf.SMS, + "Email", config.Conf.Email, + "Mysql", *config.Conf.MySQL, + "Server", *config.Conf.Server, + ) + // service init + svc := service.New(config.Conf) + httpSrv := http.Init(svc) + + // rpc init + rpc := grpc.New(config.Conf.GRPCServer, svc) + + // register server + _, port, _ := net.SplitHostPort(config.Conf.GRPCServer.Addr) + addr := fmt.Sprintf("%s:%s", ip.InternalIP(), port) + if err := naming.Register(config.Conf.Reg.RegAddrs, config.Conf.Reg.SrvName, addr, config.Conf.Reg.Schema, 15); err != nil { + panic(err) + } + fmt.Println("register ok") + + // init signal + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT) + for { + s := <-c + log.Info("service get a signal %s", s.String()) + switch s { + case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT: + // 退出时从 etcd 中删除 + if err := naming.UnRegister(config.Conf.Reg.SrvName, addr, config.Conf.Reg.Schema); err != nil { + log.Error("naming.UnRegister", "err", err) + } + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := httpSrv.Shutdown(ctx); err != nil { + log.Error("server shutdown:", "err", err) + } + if err := rpc.Shutdown(ctx); err != nil { + log.Error("rpc Shutdown", "err", err) + } + time.Sleep(time.Second * 2) + log.Info(srvName + " server exit") + return + case syscall.SIGHUP: + // TODO reload + default: + return + } + } +} diff --git a/service/backup/config/backup.toml b/service/backup/config/backup.toml new file mode 100644 index 0000000..72db8a7 --- /dev/null +++ b/service/backup/config/backup.toml @@ -0,0 +1,51 @@ +Env = "debug" + +[server] +addr = "0.0.0.0:18004" + +[Reg] +Schema = "dtalk" +SrvName = "backup" +RegAddrs = "127.0.0.1:2379" + +[GRPCServer] +Network= "tcp" +Addr= ":18012" +Timeout= "1s" +KeepAliveMaxConnectionIdle= "60s" +KeepAliveMaxConnectionAge= "2h" +KeepAliveMaxMaxConnectionAgeGrace= "20s" +KeepAliveTime= "60s" +KeepAliveTimeout= "20s" + +[MySQL] +Host = "127.0.0.1" +Port = 3306 +User = "root" +Pwd = "123456" +Db = "dtalk" + +[SMS] +Surl = "" +AppKey = "" +SecretKey = "" +Msg = "" +Env = "debug" + +[SMS.CodeTypes] +quick="quick" + +[Email] +Surl = "" +AppKey = "" +SecretKey = "" +Msg = "" +Env = "debug" + +[Email.CodeTypes] +quick="quick" + +[[Whitelist]] +Account="" +Code="" +Enable=true \ No newline at end of file diff --git a/service/backup/config/config.go b/service/backup/config/config.go new file mode 100644 index 0000000..6d19810 --- /dev/null +++ b/service/backup/config/config.go @@ -0,0 +1,132 @@ +package config + +import ( + "flag" + "time" + + "github.com/BurntSushi/toml" + "gitlab.33.cn/chat/dtalk/pkg/net/grpc" + xgrpc "gitlab.33.cn/chat/dtalk/pkg/net/grpc" + xtime "gitlab.33.cn/chat/dtalk/pkg/time" +) + +var ( + confPath string + + Conf *Config +) + +func init() { + flag.StringVar(&confPath, "conf", "backup.toml", "default config path.") +} + +// Init init config. +func Init() (err error) { + Conf = Default() + _, err = toml.DecodeFile(confPath, &Conf) + return +} + +func Default() *Config { + return &Config{ + Server: &HttpServer{ + Addr: "0.0.0.0:18004", + }, + Reg: &Reg{ + Schema: "dtalk", + SrvName: "group", + RegAddrs: "127.0.0.1:2379", + }, + GRPCServer: &xgrpc.ServerConfig{ + Network: "tcp", + Addr: ":18012", + Timeout: xtime.Duration(time.Second), + KeepAliveMaxConnectionIdle: xtime.Duration(time.Second * 60), + KeepAliveMaxConnectionAge: xtime.Duration(time.Hour * 2), + KeepAliveMaxMaxConnectionAgeGrace: xtime.Duration(time.Second * 20), + KeepAliveTime: xtime.Duration(time.Second * 60), + KeepAliveTimeout: xtime.Duration(time.Second * 20), + }, + MySQL: &MySQL{ + Host: "127.0.0.1", + Port: 3306, + User: "root", + Pwd: "123456", + Db: "dtalk", + }, + SMS: SMS{ + Surl: "", + AppKey: "", + SecretKey: "", + Msg: "", + Env: "debug", + CodeTypes: map[string]string{}, + }, + Email: Email{ + Surl: "", + AppKey: "", + SecretKey: "", + Msg: "", + Env: "debug", + CodeTypes: map[string]string{}, + }, + Whitelist: []SMSEmailWhitelist{ + {}, + }, + } +} + +type Config struct { + Env string + Server *HttpServer + Reg *Reg + //gRPC server + GRPCServer *grpc.ServerConfig + MySQL *MySQL + SMS SMS + Email Email + Whitelist []SMSEmailWhitelist +} + +type HttpServer struct { + Addr string +} + +type MySQL struct { + Host string + Port int32 + User string + Pwd string + Db string +} + +// Reg is service register/discovery config +type Reg struct { + Schema string + SrvName string // call + RegAddrs string // etcd addrs, seperate by ',' +} + +type SMS struct { + Surl string + AppKey string + SecretKey string + Msg string + Env string + CodeTypes map[string]string +} + +type Email struct { + Surl string + AppKey string + SecretKey string + Msg string + Env string + CodeTypes map[string]string +} + +type SMSEmailWhitelist struct { + Account string + Code string + Enable bool +} diff --git a/service/backup/dao/dao.go b/service/backup/dao/dao.go new file mode 100644 index 0000000..3fbbdfd --- /dev/null +++ b/service/backup/dao/dao.go @@ -0,0 +1,65 @@ +package dao + +import ( + "fmt" + + _ "github.com/go-sql-driver/mysql" + "github.com/inconshreveable/log15" + "github.com/jinzhu/gorm" + "gitlab.33.cn/chat/dtalk/service/backup/config" + "gitlab.33.cn/chat/dtalk/service/backup/model" +) + +type Dao struct { + log log15.Logger + db *gorm.DB +} + +func New(c *config.Config) *Dao { + d := &Dao{ + log: log15.New("module", "backup/dao"), + db: newDB(c.Env, c.MySQL), + } + return d +} + +func newDB(env string, cfg *config.MySQL) *gorm.DB { + db, err := gorm.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local", + cfg.User, + cfg.Pwd, + cfg.Host, + cfg.Port, + cfg.Db)) + if err != nil { + panic(err) + } + if env == model.Debug { + db.LogMode(true) + } + + gorm.DefaultTableNameHandler = func(db *gorm.DB, defaultTableName string) string { + //return setting.DatabaseSetting.TablePrefix + defaultTableName + return defaultTableName + } + + db.SingularTable(true) + //db.Callback().Create().Replace("gorm:update_time_stamp", updateTimeStampForCreateCallback) + //db.Callback().Update().Replace("gorm:update_time_stamp", updateTimeStampForUpdateCallback) + //db.Callback().Delete().Replace("gorm:delete", deleteCallback) + db.DB().SetMaxIdleConns(10) + db.DB().SetMaxOpenConns(100) + + db.Set("gorm:table_options", + "ENGINE=InnoDB AUTO_INCREMENT=1 CHARACTER SET=utf8mb4 COLLATE=utf8mb4_general_ci").AutoMigrate( + &model.AddrBackup{}, &model.AddrRelate{}, &model.AddrMove{}, + ) + return db +} + +func (d *Dao) GetTx() *gorm.DB { + return d.db.Begin() +} + +func (d *Dao) CloseDB() { + defer d.db.Close() +} diff --git a/service/backup/dao/dao_test.go b/service/backup/dao/dao_test.go new file mode 100644 index 0000000..b9bddb7 --- /dev/null +++ b/service/backup/dao/dao_test.go @@ -0,0 +1,29 @@ +package dao + +import ( + "os" + "testing" + + "github.com/gomodule/redigo/redis" + "github.com/inconshreveable/log15" + "github.com/jinzhu/gorm" + "gitlab.33.cn/chat/dtalk/service/backup/config" +) + +var ( + testLog log15.Logger + testConn *gorm.DB + testRedis *redis.Pool +) + +func TestMain(m *testing.M) { + testConn = newDB("debug", &config.MySQL{ + Host: "172.16.101.107", + Port: 3306, + User: "root", + Pwd: "123456", + Db: "dtalk", + }) + testLog = log15.New("model", "test") + os.Exit(m.Run()) +} diff --git a/service/backup/dao/db.go b/service/backup/dao/db.go new file mode 100644 index 0000000..0823e9b --- /dev/null +++ b/service/backup/dao/db.go @@ -0,0 +1,191 @@ +package dao + +import ( + "github.com/jinzhu/gorm" + "gitlab.33.cn/chat/dtalk/service/backup/model" + "time" +) + +func (d *Dao) Query(tp uint32, query string) (*model.AddrBackup, error) { + var record model.AddrBackup + sqlCase := "" + switch tp { + case model.Phone: + sqlCase = "phone = ?" + case model.Email: + sqlCase = "email = ?" + case model.Address: + sqlCase = "address = ?" + default: + return nil, model.ErrQueryType + } + err := d.db.Where(sqlCase, query).First(&record).Error + if err != nil && err != gorm.ErrRecordNotFound { + d.log.Error("Query err", "err", err, "type", tp, "query", query) + return nil, err + } else if err == gorm.ErrRecordNotFound { + return nil, nil + } + return &record, nil +} + +func (d *Dao) QueryRelate(tp uint32, query string) (*model.AddrRelate, error) { + var record model.AddrRelate + sqlCase := "" + switch tp { + case model.Phone: + sqlCase = "phone = ?" + case model.Email: + sqlCase = "email = ?" + case model.Address: + sqlCase = "address = ?" + default: + return nil, model.ErrQueryType + } + err := d.db.Where(sqlCase, query).First(&record).Error + if err != nil && err != gorm.ErrRecordNotFound { + d.log.Error("Query err", "err", err, "type", tp, "query", query) + return nil, err + } else if err == gorm.ErrRecordNotFound { + return nil, nil + } + return &record, nil +} + +func (d *Dao) createAddrBackup(r *model.AddrBackup) error { + err := d.db.Create(r).Error + if err != nil { + d.log.Error("Create AddrBackup err", "err", err, "r", r) + } + return err +} + +func (d *Dao) updatePhone(r *model.AddrBackup) error { + var records []model.AddrBackup + tx := d.db.Begin() + err := tx.Where("phone = ? AND address != ?", r.Phone, r.Address).Find(&records).Error + if err != nil && err != gorm.ErrRecordNotFound { + tx.Rollback() + return err + } + new := map[string]interface{}{"phone": "", "area": ""} + for _, record := range records { + err = tx.Model(model.AddrBackup{}).Where("address = ?", record.Address).Updates(new).Error + if err != nil { + tx.Rollback() + return err + } + } + err = tx.Model(model.AddrBackup{}).Where("address = ?", r.Address).Updates(r).Error + if err != nil { + tx.Rollback() + return err + } + tx.Commit() + return nil +} + +func (d *Dao) updatePhoneRelate(r *model.AddrRelate) error { + var records []model.AddrRelate + tx := d.db.Begin() + err := tx.Where("phone = ? AND address != ?", r.Phone, r.Address).Find(&records).Error + if err != nil && err != gorm.ErrRecordNotFound { + tx.Rollback() + return err + } + new := map[string]interface{}{"phone": "", "area": ""} + for _, record := range records { + err = tx.Model(model.AddrRelate{}).Where("address = ?", record.Address).Updates(new).Error + if err != nil { + tx.Rollback() + return err + } + } + err = tx.Model(model.AddrRelate{}).Where("address = ?", r.Address).Updates(r).Error + if err != nil { + tx.Rollback() + return err + } + tx.Commit() + return nil +} + +func (d *Dao) updateEmail(r *model.AddrBackup) error { + var records []model.AddrBackup + tx := d.db.Begin() + err := tx.Where("email = ? AND address != ?", r.Email, r.Address).Find(&records).Error + if err != nil && err != gorm.ErrRecordNotFound { + tx.Rollback() + return err + } + new := map[string]interface{}{"email": ""} + for _, record := range records { + err = tx.Model(model.AddrBackup{}).Where("address = ?", record.Address).Updates(new).Error + if err != nil { + tx.Rollback() + return err + } + } + err = tx.Model(model.AddrBackup{}).Where("address = ?", r.Address).Updates(r).Error + if err != nil { + tx.Rollback() + return err + } + tx.Commit() + return nil +} + +func (d *Dao) UpdateAddrBackup(tp uint32, r *model.AddrBackup) error { + var record model.AddrBackup + err := d.db.Where("address = ?", r.Address).First(&record).Error + if err != nil { + if err != gorm.ErrRecordNotFound { + return err + } + r.CreateTime = time.Now() + err = d.createAddrBackup(r) + if err != nil { + return err + } + } + switch tp { + case model.Phone: + return d.updatePhone(r) + case model.Email: + return d.updateEmail(r) + default: + return model.ErrQueryType + } +} + +func (d *Dao) UpdateAddrRelate(tp uint32, r *model.AddrRelate) error { + var record model.AddrRelate + err := d.db.Where("address = ?", r.Address).First(&record).Error + if err != nil { + if err != gorm.ErrRecordNotFound { + return err + } + r.CreateTime = time.Now() + err := d.db.Create(r).Error + if err != nil { + d.log.Error("Create AddrRelate err", "err", err, "r", r) + } + } + switch tp { + case model.Phone: + return d.updatePhoneRelate(r) + case model.Email: + return model.ErrQueryType + default: + return model.ErrQueryType + } +} + +func (d *Dao) UpdateMnemonic(r *model.AddrBackup) error { + err := d.db.Model(model.AddrBackup{}).Where("address = ?", r.Address).Updates(r).Error + if err != nil { + d.log.Error("UpdateMnemonic err", "err", err, "r", r) + return err + } + return nil +} diff --git a/service/backup/dao/db_test.go b/service/backup/dao/db_test.go new file mode 100644 index 0000000..eab2996 --- /dev/null +++ b/service/backup/dao/db_test.go @@ -0,0 +1,185 @@ +package dao + +import ( + "github.com/inconshreveable/log15" + "github.com/jinzhu/gorm" + "gitlab.33.cn/chat/dtalk/service/backup/model" + "testing" + "time" +) + +func TestDao_Query(t *testing.T) { + type fields struct { + log log15.Logger + db *gorm.DB + } + type args struct { + tp uint32 + query string + } + tests := []struct { + name string + fields fields + args args + want *model.AddrBackup + wantErr bool + }{ + { + name: "test query", + fields: fields{ + log: testLog, + db: testConn, + }, + args: args{ + tp: model.Phone, + query: "18217379846", + }, + want: nil, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + d := &Dao{ + log: tt.fields.log, + db: tt.fields.db, + } + got, err := d.Query(tt.args.tp, tt.args.query) + if (err != nil) != tt.wantErr { + t.Errorf("Query() error = %v, wantErr %v", err, tt.wantErr) + return + } + t.Logf("got record:%v", got) + }) + } +} + +func TestDao_UpdateAddrBackup(t *testing.T) { + type fields struct { + log log15.Logger + db *gorm.DB + } + type args struct { + tp uint32 + r *model.AddrBackup + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "test bind", + fields: fields{ + log: testLog, + db: testConn, + }, + args: args{ + tp: model.Phone, + r: &model.AddrBackup{ + Address: "test_address", + Area: "", + Phone: "test_phone", + Mnemonic: "test_mne1", + UpdateTime: time.Now(), + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + d := &Dao{ + log: tt.fields.log, + db: tt.fields.db, + } + if err := d.UpdateAddrBackup(tt.args.tp, tt.args.r); (err != nil) != tt.wantErr { + t.Errorf("UpdateAddrBackup() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestDao_UpdateAddrRelate(t *testing.T) { + type fields struct { + log log15.Logger + db *gorm.DB + } + type args struct { + tp uint32 + r *model.AddrRelate + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "test relate", + fields: fields{ + log: testLog, + db: testConn, + }, + args: args{ + tp: model.Phone, + r: &model.AddrRelate{ + Address: "test_address", + Area: "", + Phone: "test_phone2", + Mnemonic: "test_mne2", + UpdateTime: time.Now(), + }, + }, + wantErr: false, + }, + { + name: "test relate2", + fields: fields{ + log: testLog, + db: testConn, + }, + args: args{ + tp: model.Phone, + r: &model.AddrRelate{ + Address: "test_address", + Area: "", + Phone: "test_phone", + Mnemonic: "test_mne3", + UpdateTime: time.Now(), + }, + }, + wantErr: false, + }, + { + name: "test relate2", + fields: fields{ + log: testLog, + db: testConn, + }, + args: args{ + tp: model.Phone, + r: &model.AddrRelate{ + Address: "test_address2", + Area: "", + Phone: "test_phone2", + Mnemonic: "test_mne22", + UpdateTime: time.Now(), + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + d := &Dao{ + log: tt.fields.log, + db: tt.fields.db, + } + if err := d.UpdateAddrRelate(tt.args.tp, tt.args.r); (err != nil) != tt.wantErr { + t.Errorf("UpdateAddrRelate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/service/backup/dao/move.go b/service/backup/dao/move.go new file mode 100644 index 0000000..56ea8c1 --- /dev/null +++ b/service/backup/dao/move.go @@ -0,0 +1,27 @@ +package dao + +import ( + "github.com/jinzhu/gorm" + "gitlab.33.cn/chat/dtalk/service/backup/model" +) + +func (d *Dao) QueryAddressEnrolment(btyAddr string) (*model.AddrMove, error) { + var record model.AddrMove + err := d.db.Where("bty_addr = ?", btyAddr).First(&record).Error + if err != nil && err != gorm.ErrRecordNotFound { + d.log.Error("Query err", "err", err, "bty_addr", btyAddr) + return nil, err + } else if err == gorm.ErrRecordNotFound { + return nil, nil + } + return &record, nil +} + +func (d *Dao) CreateAddressEnrolment(r *model.AddrMove) error { + err := d.db.Create(r).Error + if err != nil { + d.log.Error("CreateAddressEnrolment err", "err", err, "r", r) + return err + } + return nil +} diff --git a/service/backup/dao/move_test.go b/service/backup/dao/move_test.go new file mode 100644 index 0000000..84c902e --- /dev/null +++ b/service/backup/dao/move_test.go @@ -0,0 +1,95 @@ +package dao + +import ( + "github.com/inconshreveable/log15" + "github.com/jinzhu/gorm" + "gitlab.33.cn/chat/dtalk/service/backup/model" + "testing" +) + +func TestDao_CreateAddressEnrolment(t *testing.T) { + type fields struct { + log log15.Logger + db *gorm.DB + } + type args struct { + r *model.AddrMove + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "", + fields: fields{ + log: testLog, + db: testConn, + }, + args: args{ + r: &model.AddrMove{ + BtyAddr: "testBty", + BtcAddr: "testBtc", + State: 0, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + d := &Dao{ + log: tt.fields.log, + db: tt.fields.db, + } + if err := d.CreateAddressEnrolment(tt.args.r); (err != nil) != tt.wantErr { + t.Errorf("CreateAddressEnrolment() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestDao_QueryAddressEnrolment(t *testing.T) { + type fields struct { + log log15.Logger + db *gorm.DB + } + type args struct { + btyAddr string + } + tests := []struct { + name string + fields fields + args args + want *model.AddrMove + wantErr bool + }{ + { + name: "", + fields: fields{ + log: testLog, + db: testConn, + }, + args: args{ + btyAddr: "testBty", + }, + want: nil, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + d := &Dao{ + log: tt.fields.log, + db: tt.fields.db, + } + got, err := d.QueryAddressEnrolment(tt.args.btyAddr) + if (err != nil) != tt.wantErr { + t.Errorf("QueryAddressEnrolment() error = %v, wantErr %v", err, tt.wantErr) + return + } + t.Log("got:", got) + }) + } +} diff --git a/service/backup/docs/docs.go b/service/backup/docs/docs.go new file mode 100644 index 0000000..9e52275 --- /dev/null +++ b/service/backup/docs/docs.go @@ -0,0 +1,161 @@ +// Package docs GENERATED BY THE COMMAND ABOVE; DO NOT EDIT +// This file was generated by swaggo/swag +package docs + +import ( + "bytes" + "encoding/json" + "strings" + "text/template" + + "github.com/swaggo/swag" +) + +var doc = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{escape .Description}}", + "title": "{{.Title}}", + "contact": {}, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/get-address": { + "post": { + "tags": [ + "backup" + ], + "summary": "通过手机或邮箱得到地址", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/model.GetAddressRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/model.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.GetAddressResponse" + } + } + } + ] + } + } + } + } + } + }, + "definitions": { + "model.GeneralResponse": { + "type": "object", + "properties": { + "code": { + "type": "integer" + }, + "data": { + "type": "object" + }, + "message": { + "type": "integer" + } + } + }, + "model.GetAddressRequest": { + "type": "object", + "required": [ + "query" + ], + "properties": { + "query": { + "type": "string" + } + } + }, + "model.GetAddressResponse": { + "type": "object", + "properties": { + "address": { + "type": "string" + } + } + } + } +}` + +type swaggerInfo struct { + Version string + Host string + BasePath string + Schemes []string + Title string + Description string +} + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = swaggerInfo{ + Version: "1.0", + Host: "127.0.0.1:18004", + BasePath: "", + Schemes: []string{}, + Title: "backup", + Description: "", +} + +type s struct{} + +func (s *s) ReadDoc() string { + sInfo := SwaggerInfo + sInfo.Description = strings.Replace(sInfo.Description, "\n", "\\n", -1) + + t, err := template.New("swagger_info").Funcs(template.FuncMap{ + "marshal": func(v interface{}) string { + a, _ := json.Marshal(v) + return string(a) + }, + "escape": func(v interface{}) string { + // escape tabs + str := strings.Replace(v.(string), "\t", "\\t", -1) + // replace " with \", and if that results in \\", replace that with \\\" + str = strings.Replace(str, "\"", "\\\"", -1) + return strings.Replace(str, "\\\\\"", "\\\\\\\"", -1) + }, + }).Parse(doc) + if err != nil { + return doc + } + + var tpl bytes.Buffer + if err := t.Execute(&tpl, sInfo); err != nil { + return doc + } + + return tpl.String() +} + +func init() { + swag.Register(swag.Name, &s{}) +} diff --git a/service/backup/docs/swagger.json b/service/backup/docs/swagger.json new file mode 100644 index 0000000..7b17c07 --- /dev/null +++ b/service/backup/docs/swagger.json @@ -0,0 +1,91 @@ +{ + "swagger": "2.0", + "info": { + "title": "backup", + "contact": {}, + "version": "1.0" + }, + "host": "127.0.0.1:18004", + "paths": { + "/get-address": { + "post": { + "tags": [ + "backup" + ], + "summary": "通过手机或邮箱得到地址", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/model.GetAddressRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/model.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.GetAddressResponse" + } + } + } + ] + } + } + } + } + } + }, + "definitions": { + "model.GeneralResponse": { + "type": "object", + "properties": { + "code": { + "type": "integer" + }, + "data": { + "type": "object" + }, + "message": { + "type": "integer" + } + } + }, + "model.GetAddressRequest": { + "type": "object", + "required": [ + "query" + ], + "properties": { + "query": { + "type": "string" + } + } + }, + "model.GetAddressResponse": { + "type": "object", + "properties": { + "address": { + "type": "string" + } + } + } + } +} \ No newline at end of file diff --git a/service/backup/docs/swagger.yaml b/service/backup/docs/swagger.yaml new file mode 100644 index 0000000..fd28921 --- /dev/null +++ b/service/backup/docs/swagger.yaml @@ -0,0 +1,55 @@ +definitions: + model.GeneralResponse: + properties: + code: + type: integer + data: + type: object + message: + type: integer + type: object + model.GetAddressRequest: + properties: + query: + type: string + required: + - query + type: object + model.GetAddressResponse: + properties: + address: + type: string + type: object +host: 127.0.0.1:18004 +info: + contact: {} + title: backup + version: "1.0" +paths: + /get-address: + post: + parameters: + - description: MOCK + in: header + name: FZM-SIGNATURE + required: true + type: string + - description: body + in: body + name: data + schema: + $ref: '#/definitions/model.GetAddressRequest' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/model.GeneralResponse' + - properties: + data: + $ref: '#/definitions/model.GetAddressResponse' + type: object + summary: 通过手机或邮箱得到地址 + tags: + - backup +swagger: "2.0" diff --git a/service/backup/model/api.go b/service/backup/model/api.go new file mode 100644 index 0000000..1b82f60 --- /dev/null +++ b/service/backup/model/api.go @@ -0,0 +1,15 @@ +package model + +type GeneralResponse struct { + Code int `json:"code"` + Message int `json:"message"` + Data interface{} `json:"data"` +} + +type GetAddressRequest struct { + Query string `json:"query" binding:"required"` +} + +type GetAddressResponse struct { + Address string `json:"address"` +} diff --git a/service/backup/model/backup.go b/service/backup/model/backup.go new file mode 100644 index 0000000..c450df6 --- /dev/null +++ b/service/backup/model/backup.go @@ -0,0 +1,35 @@ +package model + +import "time" + +type AddrBackup struct { + Address string `json:"address" gorm:"primary_key;column:address;type:varchar(255);comment:'用户地址'"` + Area string `json:"area" gorm:"column:area;type:varchar(4);comment:'区号'"` + Phone string `json:"phone" gorm:"column:phone;type:varchar(11);comment:'手机号'"` + Email string `json:"email" gorm:"column:email;type:varchar(30);comment:'邮箱'"` + Mnemonic string `json:"mnemonic" gorm:"column:mnemonic;type:varchar(1020);comment:'助记词'"` + PrivateKey string `json:"private_key" gorm:"column:private_key;type:varchar(1020);comment:'加密私钥'"` + UpdateTime time.Time `json:"update_time" gorm:"column:update_time;type:datetime;comment:'更新时间'"` + CreateTime time.Time `json:"create_time" gorm:"column:create_time;type:datetime;comment:'创建时间'"` +} + +// TableName returns the table name of the DtalkAddrBackup model +func (d *AddrBackup) TableName() string { + return "dtalk_addr_backup" +} + +type AddrRelate struct { + Address string `json:"address" gorm:"primary_key;column:address;type:varchar(255);comment:'用户地址'"` + Area string `json:"area" gorm:"column:area;type:varchar(4);comment:'区号'"` + Phone string `json:"phone" gorm:"column:phone;type:varchar(11);comment:'手机号'"` + Email string `json:"email" gorm:"column:email;type:varchar(30);comment:'邮箱'"` + Mnemonic string `json:"mnemonic" gorm:"column:mnemonic;type:varchar(1020);comment:'助记词'"` + PrivateKey string `json:"private_key" gorm:"column:private_key;type:varchar(1020);comment:'加密私钥'"` + UpdateTime time.Time `json:"update_time" gorm:"column:update_time;type:datetime;comment:'更新时间'"` + CreateTime time.Time `json:"create_time" gorm:"column:create_time;type:datetime;comment:'创建时间'"` +} + +// TableName returns the table name of the DtalkAddrBackup model +func (d *AddrRelate) TableName() string { + return "dtalk_addr_relate" +} diff --git a/service/backup/model/const.go b/service/backup/model/const.go new file mode 100644 index 0000000..8bf2326 --- /dev/null +++ b/service/backup/model/const.go @@ -0,0 +1,29 @@ +package model + +const ( + Debug = "debug" + Release = "release" +) + +const ( + Phone = 0 + Email = 1 + Address = 2 +) + +const ( + ParamSvcUrl = "serviceUrl" + ParamCodeType = "codetype" + ParamMobile = "mobile" + ParamEmail = "email" + ParamMsg = "msg" + ParamTicket = "ticket" + ParamBizId = "biz" + ParamCode = "code" +) + +const ( + Quick = "quick" + Bind = "bind" + Export = "export" +) diff --git a/service/backup/model/error.go b/service/backup/model/error.go new file mode 100644 index 0000000..645286a --- /dev/null +++ b/service/backup/model/error.go @@ -0,0 +1,7 @@ +package model + +import "errors" + +var ( + ErrQueryType = errors.New("query type no support") +) diff --git a/service/backup/model/move.go b/service/backup/model/move.go new file mode 100644 index 0000000..68e2cbc --- /dev/null +++ b/service/backup/model/move.go @@ -0,0 +1,12 @@ +package model + +type AddrMove struct { + BtyAddr string `json:"bty_addr" gorm:"primary_key;column:bty_addr;type:varchar(255);comment:'bty地址'"` + BtcAddr string `json:"btc_addr" gorm:"column:btc_addr;type:varchar(255);not null;comment:'btc地址'"` + State int32 `json:"state" gorm:"column:state;type:tinyint(255);comment:'0->对应关系已建立;1-> 好友关系已迁移'"` +} + +// TableName returns the table name of the DtalkAddrMove model +func (d *AddrMove) TableName() string { + return "dtalk_addr_move" +} diff --git a/service/backup/model/query.go b/service/backup/model/query.go new file mode 100644 index 0000000..c28b84d --- /dev/null +++ b/service/backup/model/query.go @@ -0,0 +1,48 @@ +package model + +import "regexp" + +type Query struct { + Tp int + Query string +} + +func NewQuery(str string) *Query { + tp, query := VerifyQueryFormat(str) + return &Query{ + Tp: tp, + Query: query, + } +} + +func VerifyQueryFormat(query string) (int, string) { + if checkEmail(query) { + return Email, query + } + if checkPhone(query) { + return Phone, query + } + if checkAddress(query) { + return Address, query + } + return -1, "" +} + +func checkEmail(email string) bool { + //pattern := `\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*` //匹配电子邮箱 + pattern := `^[0-9a-z][_.0-9a-z-]{0,31}@([0-9a-z][0-9a-z-]{0,30}[0-9a-z]\.){1,4}[a-z]{2,4}$` + reg := regexp.MustCompile(pattern) + return reg.MatchString(email) +} + +func checkPhone(phone string) bool { + //regular := "^1(3\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\d|9[0-35-9])\d{8}$" + // + //reg := regexp.MustCompile(regular) + //return reg.MatchString(phone) + return len(phone) == 11 +} + +func checkAddress(address string) bool { + return len(address) == 34 +} diff --git a/service/backup/model/validate.go b/service/backup/model/validate.go new file mode 100644 index 0000000..ead6ca6 --- /dev/null +++ b/service/backup/model/validate.go @@ -0,0 +1,6 @@ +package model + +type Validate interface { + Send(map[string]string) (interface{}, error) + ValidateCode(map[string]string) error +} diff --git a/service/backup/server/grpc/server.go b/service/backup/server/grpc/server.go new file mode 100644 index 0000000..a2c4597 --- /dev/null +++ b/service/backup/server/grpc/server.go @@ -0,0 +1,66 @@ +package grpc + +import ( + "context" + "errors" + "time" + + xgrpc "gitlab.33.cn/chat/dtalk/pkg/net/grpc" + pb "gitlab.33.cn/chat/dtalk/service/backup/api" + "gitlab.33.cn/chat/dtalk/service/backup/model" + "gitlab.33.cn/chat/dtalk/service/backup/service" + "google.golang.org/grpc" +) + +func New(c *xgrpc.ServerConfig, svr *service.Service) *xgrpc.Server { + connectionTimeout := grpc.ConnectionTimeout(time.Duration(c.Timeout)) + ws := xgrpc.NewServer(c, connectionTimeout) + pb.RegisterBackupServer(ws.Server(), &server{pb.UnimplementedBackupServer{}, svr}) + ws, err := ws.Start() + if err != nil { + panic(err) + } + return ws +} + +type server struct { + pb.UnimplementedBackupServer + svr *service.Service +} + +func (s *server) Retrieve(ctx context.Context, req *pb.RetrieveReq) (*pb.RetrieveReply, error) { + if req == nil { + return &pb.RetrieveReply{}, errors.New("bad request") + } + var addrBackup *model.AddrBackup + var err error + switch req.Type { + case pb.QueryType_Address: + addrBackup, err = s.svr.AddressRetrieve(req.Val) + case pb.QueryType_Phone: + addrBackup, err = s.svr.AddressRetrieve(req.Val) + case pb.QueryType_Email: + addrBackup, err = s.svr.AddressRetrieve(req.Val) + } + if err != nil { + return &pb.RetrieveReply{}, err + } + if addrBackup == nil { + return nil, errors.New("record not found") + } + return toRetrieve(addrBackup), err +} + +// toBindInfo Account, Bind类型转 BindInfo +func toRetrieve(src *model.AddrBackup) *pb.RetrieveReply { + return &pb.RetrieveReply{ + Address: src.Address, + Area: src.Area, + Phone: src.Phone, + Email: src.Email, + Mnemonic: src.Mnemonic, + PrivateKey: src.PrivateKey, + UpdateTime: src.UpdateTime.Unix(), + CreateTime: src.UpdateTime.Unix(), + } +} diff --git a/service/backup/server/http/backup.go b/service/backup/server/http/backup.go new file mode 100644 index 0000000..ada8951 --- /dev/null +++ b/service/backup/server/http/backup.go @@ -0,0 +1,381 @@ +package http + +import ( + "github.com/gin-gonic/gin" + "gitlab.33.cn/chat/dtalk/pkg/api" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/service/backup/model" +) + +//get all nodes +func QueryPhone(c *gin.Context) { + var params struct { + Area string `json:"area"` + Phone string `json:"phone" binding:"required"` + } + if err := c.ShouldBindJSON(¶ms); err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + b, err := svc.PhoneIsBound(params.Area, params.Phone) + ret := make(map[string]interface{}) + ret["exists"] = b + c.Set(api.ReqResult, ret) + c.Set(api.ReqError, err) +} + +func QueryEmail(c *gin.Context) { + var params struct { + Email string `json:"email" binding:"required"` + } + if err := c.ShouldBindJSON(¶ms); err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + b, err := svc.EmailIsBound(params.Email) + ret := make(map[string]interface{}) + ret["exists"] = b + c.Set(api.ReqResult, ret) + c.Set(api.ReqError, err) +} + +//@deprecated +func SendPhoneCode(c *gin.Context) { + var params struct { + Phone string `json:"phone" binding:"required"` + //图形验证码所需 + //Ticket string `json:"ticket"` + //BusinessId string `json:"businessId"` + } + if err := c.ShouldBindJSON(¶ms); err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + err := svc.SendPhoneCode(model.Quick, params.Phone) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.VerifyCodeSendError).SetExtMessage(err.Error())) + return + } + c.Set(api.ReqError, nil) +} + +func SendPhoneCodeV2(c *gin.Context) { + var params struct { + CodeType string `json:"codeType" binding:"required"` + Phone string `json:"phone" binding:"required"` + //图形验证码所需 + //Ticket string `json:"ticket"` + //BusinessId string `json:"businessId"` + } + if err := c.ShouldBindJSON(¶ms); err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + err := svc.SendPhoneCode(params.CodeType, params.Phone) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.VerifyCodeSendError).SetExtMessage(err.Error())) + return + } + c.Set(api.ReqError, nil) +} + +//@deprecated +func SendEmailCode(c *gin.Context) { + var params struct { + Email string `json:"email" binding:"required"` + //图形验证码所需 + //Ticket string `json:"ticket"` + //BusinessId string `json:"businessId"` + } + if err := c.ShouldBindJSON(¶ms); err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + err := svc.SendEmailCode(model.Quick, params.Email) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.VerifyCodeSendError).SetExtMessage(err.Error())) + return + } + c.Set(api.ReqError, nil) +} + +func SendEmailCodeV2(c *gin.Context) { + var params struct { + CodeType string `json:"codeType" binding:"required"` + Email string `json:"email" binding:"required"` + //图形验证码所需 + //Ticket string `json:"ticket"` + //BusinessId string `json:"businessId"` + } + if err := c.ShouldBindJSON(¶ms); err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + err := svc.SendEmailCode(params.CodeType, params.Email) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.VerifyCodeSendError).SetExtMessage(err.Error())) + return + } + c.Set(api.ReqError, nil) +} + +//@deprecated +func PhoneBinding(c *gin.Context) { + var params struct { + Area string `json:"area"` + Phone string `json:"phone" binding:"required"` + Code string `json:"code" binding:"required"` + Mnemonic string `json:"mnemonic" binding:"required"` + } + if err := c.ShouldBindJSON(¶ms); err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + addr := c.MustGet(api.Address).(string) + err := svc.PhoneBinding(addr, params.Area, params.Phone, params.Code, params.Mnemonic) + if err != nil { + c.Set(api.ReqError, err) + return + } + + ret := make(map[string]interface{}) + ret["address"] = c.MustGet(api.Address) + c.Set(api.ReqResult, ret) + c.Set(api.ReqError, nil) +} + +func PhoneBindingV2(c *gin.Context) { + var params struct { + Area string `json:"area"` + Phone string `json:"phone" binding:"required"` + Code string `json:"code" binding:"required"` + Mnemonic string `json:"mnemonic" binding:"required"` + } + if err := c.ShouldBindJSON(¶ms); err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + addr := c.MustGet(api.Address).(string) + err := svc.PhoneBindingV2(addr, params.Area, params.Phone, params.Code, params.Mnemonic) + if err != nil { + c.Set(api.ReqError, err) + return + } + + ret := make(map[string]interface{}) + ret["address"] = c.MustGet(api.Address) + c.Set(api.ReqResult, ret) + c.Set(api.ReqError, nil) +} + +func PhoneExport(c *gin.Context) { + var params struct { + Area string `json:"area"` + Phone string `json:"phone" binding:"required"` + Code string `json:"code" binding:"required"` + Address string `json:"address" binding:"required"` + } + if err := c.ShouldBindJSON(¶ms); err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + b, err := svc.PhoneExport(params.Address, params.Area, params.Phone, params.Code) + if err != nil { + c.Set(api.ReqError, err) + return + } + + if !b { + err = xerror.NewError(xerror.ExportAddressPhoneInconsistent) + } + c.Set(api.ReqResult, nil) + c.Set(api.ReqError, err) +} + +func PhoneRelate(c *gin.Context) { + var params struct { + Area string `json:"area"` + Phone string `json:"phone" binding:"required"` + Mnemonic string `json:"mnemonic" binding:"required"` + } + if err := c.ShouldBindJSON(¶ms); err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + addr := c.MustGet(api.Address).(string) + err := svc.PhoneRelate(addr, params.Area, params.Phone, params.Mnemonic) + if err != nil { + c.Set(api.ReqError, err) + return + } + + ret := make(map[string]interface{}) + ret["address"] = c.MustGet(api.Address) + c.Set(api.ReqResult, ret) + c.Set(api.ReqError, nil) +} + +func EmailBinding(c *gin.Context) { + var params struct { + Email string `json:"email" binding:"required"` + Code string `json:"code" binding:"required"` + Mnemonic string `json:"mnemonic" binding:"required"` + } + if err := c.ShouldBindJSON(¶ms); err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + addr := c.MustGet(api.Address).(string) + + err := svc.EmailBinding(addr, params.Email, params.Code, params.Mnemonic) + if err != nil { + c.Set(api.ReqError, err) + return + } + + ret := make(map[string]interface{}) + ret["address"] = c.MustGet(api.Address) + c.Set(api.ReqResult, ret) + c.Set(api.ReqError, nil) +} + +func EmailBindingV2(c *gin.Context) { + var params struct { + Email string `json:"email" binding:"required"` + Code string `json:"code" binding:"required"` + Mnemonic string `json:"mnemonic" binding:"required"` + } + if err := c.ShouldBindJSON(¶ms); err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + addr := c.MustGet(api.Address).(string) + + err := svc.EmailBindingV2(addr, params.Email, params.Code, params.Mnemonic) + if err != nil { + c.Set(api.ReqError, err) + return + } + + ret := make(map[string]interface{}) + ret["address"] = c.MustGet(api.Address) + c.Set(api.ReqResult, ret) + c.Set(api.ReqError, nil) +} + +func EmailExport(c *gin.Context) { + var params struct { + Email string `json:"email" binding:"required"` + Code string `json:"code" binding:"required"` + Address string `json:"address" binding:"required"` + } + if err := c.ShouldBindJSON(¶ms); err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + b, err := svc.EmailExport(params.Address, params.Email, params.Code) + if err != nil { + c.Set(api.ReqError, err) + return + } + if !b { + err = xerror.NewError(xerror.ExportAddressEmailInconsistent) + } + c.Set(api.ReqResult, nil) + c.Set(api.ReqError, err) +} + +func PhoneRetrieve(c *gin.Context) { + var params struct { + Area string `json:"area"` + Phone string `json:"phone" binding:"required"` + Code string `json:"code" binding:"required"` + } + if err := c.ShouldBindJSON(¶ms); err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + //检查手机号 + item, err := svc.PhoneRetrieve(params.Area, params.Phone, params.Code) + if err != nil { + c.Set(api.ReqError, err) + return + } + c.Set(api.ReqResult, item) + c.Set(api.ReqError, nil) +} + +func EmailRetrieve(c *gin.Context) { + var params struct { + Email string `json:"email" binding:"required"` + Code string `json:"code" binding:"required"` + } + if err := c.ShouldBindJSON(¶ms); err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + //检查手机号 + item, err := svc.EmailRetrieve(params.Email, params.Code) + if err != nil { + c.Set(api.ReqError, err) + return + } + c.Set(api.ReqResult, item) + c.Set(api.ReqError, nil) +} + +func AddressRetrieve(c *gin.Context) { + addr := c.MustGet(api.Address).(string) + //检查手机号 + item, err := svc.AddressRetrieve(addr) + if err != nil { + c.Set(api.ReqError, err) + return + } + c.Set(api.ReqResult, item) + c.Set(api.ReqError, nil) +} + +func EditMnemonic(c *gin.Context) { + var params struct { + Mnemonic string `json:"mnemonic" binding:"required"` + } + if err := c.ShouldBindJSON(¶ms); err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + addr := c.MustGet(api.Address).(string) + + err := svc.EditMnemonic(addr, params.Mnemonic) + c.Set(api.ReqError, err) +} + +// @Summary 通过手机或邮箱得到地址 +// @Author chy@33.cn +// @Tags backup +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body model.GetAddressRequest false "body" +// @Success 200 {object} model.GeneralResponse{data=model.GetAddressResponse} +// @Router /get-address [post] +func GetAddress(c *gin.Context) { + req := &model.GetAddressRequest{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + res, err := svc.GetAddress(req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) +} diff --git a/service/backup/server/http/http.go b/service/backup/server/http/http.go new file mode 100644 index 0000000..aa843b9 --- /dev/null +++ b/service/backup/server/http/http.go @@ -0,0 +1,101 @@ +package http + +import ( + "github.com/gin-gonic/gin" + "github.com/inconshreveable/log15" + "gitlab.33.cn/chat/dtalk/pkg/api" + "gitlab.33.cn/chat/dtalk/service/backup/service" + "net/http" + + swaggerFiles "github.com/swaggo/files" + ginSwagger "github.com/swaggo/gin-swagger" + _ "gitlab.33.cn/chat/dtalk/service/backup/docs" +) + +var ( + svc *service.Service + log = log15.New("module", "backup/http") +) + +func Init(s *service.Service) *http.Server { + addr := s.Config().Server.Addr + env := s.Config().Env + gin.SetMode(env) + engine := Default() + InitService(s) + SetupEngine(engine) + + srv := &http.Server{ + Addr: addr, + Handler: engine, + } + go func() { + // service connections + if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Error("engineInner.Start() error(%v)", err) + panic(err) + } + }() + return srv +} + +// Default returns an Engine instance with the Logger and Recovery middleware already attached. +func Default() *gin.Engine { + router := gin.New() + // LoggerWithFormatter middleware will write the logs to gin.DefaultWriter + // By default gin.DefaultWriter = os.Stdout + router.Use(gin.LoggerWithFormatter(api.Chat33GinLogFormatter)) + router.Use(gin.Recovery()) + return router +} + +func InitService(s *service.Service) { + svc = s +} + +// @title backup +// @version 1.0 +//@host 127.0.0.1:18004 +func SetupEngine(e *gin.Engine) *gin.Engine { + root := e.Group("/", api.RespMiddleWare()) + //获取服务器列表 + root.Any("/phone-query", QueryPhone) + root.Any("/email-query", QueryEmail) + + //@deprecated + root.POST("/phone-send", SendPhoneCode) + root.POST("/email-send", SendEmailCode) + root.POST("/phone-retrieve", PhoneRetrieve) + root.POST("/email-retrieve", EmailRetrieve) + //@deprecated end + + root.POST("/v2/phone-send", SendPhoneCodeV2) + root.POST("/v2/email-send", SendEmailCodeV2) + root.POST("/v2/phone-retrieve", PhoneRetrieve) + root.POST("/v2/email-retrieve", EmailRetrieve) + root.POST("/v2/phone-export", PhoneExport) + root.POST("/v2/email-export", EmailExport) + root.Use(api.AuthMiddleWare()) + { + //@deprecated + root.POST("/phone-binding", PhoneBinding) + root.POST("/email-binding", EmailBinding) + //@deprecated end + + root.POST("/v2/phone-binding", PhoneBindingV2) + root.POST("/v2/email-binding", EmailBindingV2) + root.POST("/phone-relate", PhoneRelate) + root.POST("/address-retrieve", AddressRetrieve) + root.POST("/edit-mnemonic", EditMnemonic) + root.POST("/get-address", GetAddress) + } + + move := root.Group("transform") + move.Use(api.AuthMiddleWare()) + { + move.POST("/addressEnrolment", AddressEnrolment) + } + + e.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) + return e +} diff --git a/service/backup/server/http/move.go b/service/backup/server/http/move.go new file mode 100644 index 0000000..9766a5a --- /dev/null +++ b/service/backup/server/http/move.go @@ -0,0 +1,23 @@ +package http + +import ( + "github.com/gin-gonic/gin" + "gitlab.33.cn/chat/dtalk/pkg/api" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" +) + +// 阶段一:登记各个地址关系 +func AddressEnrolment(c *gin.Context) { + var params struct { + BtcAddress string `json:"btcAddress" binding:"required"` + EthAddress string `json:"ethAddress" binding:"required"` + } + if err := c.ShouldBindJSON(¶ms); err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + btyAddress := c.MustGet(api.Address).(string) + err := svc.AddressEnrolment(btyAddress, params.BtcAddress) + c.Set(api.ReqResult, nil) + c.Set(api.ReqError, err) +} diff --git a/service/backup/service/backup.go b/service/backup/service/backup.go new file mode 100644 index 0000000..2ec65ce --- /dev/null +++ b/service/backup/service/backup.go @@ -0,0 +1,303 @@ +package service + +import ( + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/service/backup/model" + "time" +) + +func (s *Service) EditMnemonic(addr, mne string) error { + err := s.dao.UpdateMnemonic(&model.AddrBackup{ + Address: addr, + Mnemonic: mne, + UpdateTime: time.Now(), + }) + if err != nil { + return xerror.NewError(xerror.ExecFailed) + } + return nil +} + +func (s *Service) AddressRetrieve(addr string) (*model.AddrBackup, error) { + item, err := s.dao.Query(model.Address, addr) + if err != nil { + return nil, xerror.NewError(xerror.QueryFailed) + } + return item, nil +} + +func (s *Service) PhoneIsBound(area, phone string) (bool, error) { + item, err := s.dao.Query(model.Phone, phone) + if err != nil { + return false, xerror.NewError(xerror.QueryFailed) + } + if item == nil { + itemRelate, err := s.dao.QueryRelate(model.Phone, phone) + if err != nil { + return false, xerror.NewError(xerror.QueryFailed) + } + return itemRelate != nil, nil + } + return item != nil, nil +} + +func (s *Service) SendPhoneCode(codeType, phone string) error { + //发送短信验证码 + params := map[string]string{ + model.ParamMobile: phone, + model.ParamCodeType: s.cfg.SMS.CodeTypes[codeType], + } + _, err := s.smsValidate.Send(params) + return err +} + +func (s *Service) PhoneBinding(addr, area, phone, code, mnemonic string) error { + params := map[string]string{ + model.ParamMobile: phone, + model.ParamCode: code, + model.ParamCodeType: s.cfg.SMS.CodeTypes[model.Quick], + } + //验证 + err := s.smsValidate.ValidateCode(params) + if err != nil { + return xerror.NewError(xerror.VerifyCodeError).SetExtMessage(err.Error()) + } + //绑定 + err = s.dao.UpdateAddrBackup(model.Phone, &model.AddrBackup{ + Address: addr, + Area: area, + Phone: phone, + Mnemonic: mnemonic, + UpdateTime: time.Now(), + }) + if err != nil { + return xerror.NewError(xerror.ExecFailed) + } + return nil +} + +func (s *Service) PhoneBindingV2(addr, area, phone, code, mnemonic string) error { + params := map[string]string{ + model.ParamMobile: phone, + model.ParamCode: code, + model.ParamCodeType: s.cfg.SMS.CodeTypes[model.Bind], + } + //验证 + err := s.smsValidate.ValidateCode(params) + if err != nil { + return xerror.NewError(xerror.VerifyCodeError).SetExtMessage(err.Error()) + } + //绑定 + err = s.dao.UpdateAddrBackup(model.Phone, &model.AddrBackup{ + Address: addr, + Area: area, + Phone: phone, + Mnemonic: mnemonic, + UpdateTime: time.Now(), + }) + if err != nil { + return xerror.NewError(xerror.ExecFailed) + } + return nil +} + +//关联手机号 +func (s *Service) PhoneRelate(addr, area, phone, mnemonic string) error { + //绑定 + err := s.dao.UpdateAddrRelate(model.Phone, &model.AddrRelate{ + Address: addr, + Area: area, + Phone: phone, + Mnemonic: mnemonic, + UpdateTime: time.Now(), + }) + if err != nil { + return xerror.NewError(xerror.ExecFailed) + } + return nil +} + +func (s *Service) PhoneExport(addr, area, phone, code string) (bool, error) { + params := map[string]string{ + model.ParamMobile: phone, + model.ParamCode: code, + model.ParamCodeType: s.cfg.SMS.CodeTypes[model.Export], + } + //验证 + err := s.smsValidate.ValidateCode(params) + if err != nil { + return false, xerror.NewError(xerror.VerifyCodeError).SetExtMessage(err.Error()) + } + //查询 + //item, err := s.dao.Query(model.Phone, phone) + //if err != nil { + // return false, xerror.NewError(xerror.QueryFailed) + //} + return true, nil +} + +func (s *Service) PhoneRetrieve(area, phone, code string) (*model.AddrBackup, error) { + //验证 + params := map[string]string{ + model.ParamMobile: phone, + model.ParamCode: code, + model.ParamCodeType: s.cfg.Email.CodeTypes[model.Quick], + } + err := s.smsValidate.ValidateCode(params) + if err != nil { + return nil, xerror.NewError(xerror.VerifyCodeError).SetExtMessage(err.Error()) + } + item, err := s.dao.Query(model.Phone, phone) + if err != nil { + return nil, xerror.NewError(xerror.QueryFailed) + } + if item == nil { + //查询 是否关联 + item, err := s.dao.QueryRelate(model.Phone, phone) + if err != nil || item == nil { + return nil, err + } + newAddr := &model.AddrBackup{ + Address: item.Address, + Area: item.Area, + Phone: item.Phone, + Email: item.Email, + Mnemonic: item.Mnemonic, + PrivateKey: item.PrivateKey, + UpdateTime: item.UpdateTime, + CreateTime: item.CreateTime, + } + err = s.dao.UpdateAddrBackup(model.Phone, newAddr) + if err != nil { + return nil, err + } + return newAddr, nil + } + return item, nil +} + +//--------------------email-------------// +func (s *Service) EmailIsBound(email string) (bool, error) { + item, err := s.dao.Query(model.Email, email) + if err != nil { + return false, xerror.NewError(xerror.QueryFailed) + } + return item != nil, nil +} + +func (s *Service) EmailBinding(addr, email, code, mnemonic string) error { + params := map[string]string{ + model.ParamEmail: email, + model.ParamCode: code, + model.ParamCodeType: s.cfg.Email.CodeTypes[model.Quick], + } + //验证 + err := s.emailValidate.ValidateCode(params) + if err != nil { + return xerror.NewError(xerror.VerifyCodeError).SetExtMessage(err.Error()) + } + //绑定 + err = s.dao.UpdateAddrBackup(model.Email, &model.AddrBackup{ + Address: addr, + Email: email, + Mnemonic: mnemonic, + UpdateTime: time.Now(), + }) + if err != nil { + return xerror.NewError(xerror.ExecFailed) + } + return nil +} + +func (s *Service) EmailBindingV2(addr, email, code, mnemonic string) error { + params := map[string]string{ + model.ParamEmail: email, + model.ParamCode: code, + model.ParamCodeType: s.cfg.Email.CodeTypes[model.Bind], + } + //验证 + err := s.emailValidate.ValidateCode(params) + if err != nil { + return xerror.NewError(xerror.VerifyCodeError).SetExtMessage(err.Error()) + } + //绑定 + err = s.dao.UpdateAddrBackup(model.Email, &model.AddrBackup{ + Address: addr, + Email: email, + Mnemonic: mnemonic, + UpdateTime: time.Now(), + }) + if err != nil { + return xerror.NewError(xerror.ExecFailed) + } + return nil +} + +func (s *Service) EmailExport(addr, email, code string) (bool, error) { + params := map[string]string{ + model.ParamEmail: email, + model.ParamCode: code, + model.ParamCodeType: s.cfg.Email.CodeTypes[model.Export], + } + //验证 + err := s.emailValidate.ValidateCode(params) + if err != nil { + return false, xerror.NewError(xerror.VerifyCodeError).SetExtMessage(err.Error()) + } + ////查询 + //item, err := s.dao.Query(model.Email, email) + //if err != nil { + // return false, xerror.NewError(xerror.QueryFailed) + //} + return true, nil +} + +func (s *Service) SendEmailCode(codeType, email string) error { + //发送短信验证码 + params := map[string]string{ + model.ParamEmail: email, + model.ParamCodeType: s.cfg.Email.CodeTypes[codeType], + } + _, err := s.emailValidate.Send(params) + return err +} + +func (s *Service) EmailRetrieve(email, code string) (*model.AddrBackup, error) { + //验证 + params := map[string]string{ + model.ParamEmail: email, + model.ParamCode: code, + model.ParamCodeType: s.cfg.Email.CodeTypes[model.Quick], + } + err := s.emailValidate.ValidateCode(params) + if err != nil { + return nil, xerror.NewError(xerror.VerifyCodeError).SetExtMessage(err.Error()) + } + item, err := s.dao.Query(model.Email, email) + if err != nil { + return nil, xerror.NewError(xerror.QueryFailed) + } + return item, nil +} + +func (s *Service) GetAddress(req *model.GetAddressRequest) (res *model.GetAddressResponse, err error) { + defer func() { + if err != nil && + err.Error() != xerror.NewError(xerror.ParamsError).Error() && + err.Error() != xerror.NewError(xerror.QueryNotExist).Error() { + s.log.Error("GetAddress", "err", err, "req", req) + } + }() + query := model.NewQuery(req.Query) + addrBackup, err := s.dao.Query(uint32(query.Tp), query.Query) + if err != nil { + if err == model.ErrQueryType { + return nil, xerror.NewError(xerror.ParamsError) + } + return nil, xerror.NewError(xerror.QueryFailed).SetExtMessage(err.Error()) + } + if addrBackup == nil { + return nil, xerror.NewError(xerror.QueryNotExist) + } + return &model.GetAddressResponse{Address: addrBackup.Address}, nil +} diff --git a/service/backup/service/backup_test.go b/service/backup/service/backup_test.go new file mode 100644 index 0000000..c32d061 --- /dev/null +++ b/service/backup/service/backup_test.go @@ -0,0 +1,136 @@ +package service + +import ( + "github.com/inconshreveable/log15" + "gitlab.33.cn/chat/dtalk/service/backup/config" + "gitlab.33.cn/chat/dtalk/service/backup/dao" + "gitlab.33.cn/chat/dtalk/service/backup/model" + "testing" +) + +func TestService_PhoneIsBound(t *testing.T) { + type fields struct { + log log15.Logger + cfg *config.Config + dao *dao.Dao + smsValidate model.Validate + emailValidate model.Validate + } + type args struct { + area string + phone string + } + tests := []struct { + name string + fields fields + args args + want bool + wantErr bool + }{ + { + name: "test phone is bound", + fields: fields{ + log: testLog, + cfg: testCfg, + dao: testDao, + smsValidate: testSmsValidate, + emailValidate: testEmailValidate, + }, + args: args{ + area: "", + phone: "18217379846", + }, + want: false, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &Service{ + log: tt.fields.log, + cfg: tt.fields.cfg, + dao: tt.fields.dao, + smsValidate: tt.fields.smsValidate, + emailValidate: tt.fields.emailValidate, + } + got, err := s.PhoneIsBound(tt.args.area, tt.args.phone) + if (err != nil) != tt.wantErr { + t.Errorf("PhoneIsBound() error = %v, wantErr %v", err, tt.wantErr) + return + } + t.Logf("got exists:%v", got) + }) + } +} + +func TestService_PhoneRetrieve(t *testing.T) { + type fields struct { + log log15.Logger + cfg *config.Config + dao *dao.Dao + smsValidate model.Validate + emailValidate model.Validate + } + type args struct { + area string + phone string + code string + } + tests := []struct { + name string + fields fields + args args + want *model.AddrBackup + wantErr bool + }{ + { + name: "", + fields: fields{ + log: testLog, + cfg: testCfg, + dao: testDao, + smsValidate: testSmsValidate, + emailValidate: testEmailValidate, + }, + args: args{ + area: "", + phone: "test_phone", + code: "111111", + }, + wantErr: false, + }, + { + name: "", + fields: fields{ + log: testLog, + cfg: testCfg, + dao: testDao, + smsValidate: testSmsValidate, + emailValidate: testEmailValidate, + }, + args: args{ + area: "", + phone: "test_phone2", + code: "111111", + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &Service{ + log: tt.fields.log, + cfg: tt.fields.cfg, + dao: tt.fields.dao, + smsValidate: tt.fields.smsValidate, + emailValidate: tt.fields.emailValidate, + } + got, err := s.PhoneRetrieve(tt.args.area, tt.args.phone, tt.args.code) + if (err != nil) != tt.wantErr { + t.Errorf("PhoneRetrieve() error = %v, wantErr %v", err, tt.wantErr) + return + } + t.Log(got) + }) + } +} diff --git a/service/backup/service/debug/sms.go b/service/backup/service/debug/sms.go new file mode 100644 index 0000000..318e9d1 --- /dev/null +++ b/service/backup/service/debug/sms.go @@ -0,0 +1,56 @@ +package debug + +import ( + "errors" + "gitlab.33.cn/chat/dtalk/service/backup/model" + "regexp" + "strconv" + "strings" +) + +type DebugValidate struct { + mockCode string + real model.Validate +} + +func NewDebugValidate(code string, v model.Validate) *DebugValidate { + return &DebugValidate{ + mockCode: code, + real: v, + } +} + +func GetMockCode(mode string) string { + defCode := "111111" + reg := regexp.MustCompile(`^FzmRandom(\d+)$`) + + //找出子串 + ret := reg.FindStringSubmatch(mode) + if len(ret) < 2 { + return defCode + } + + num, err := strconv.Atoi(ret[1]) + if err != nil || num == 0 { + return defCode + } + return strings.Repeat("1", num) +} + +func (v *DebugValidate) Send(params map[string]string) (interface{}, error) { + if v.real == nil { + return nil, errors.New("未注册验证器") + } + return v.real.Send(params) +} + +func (v *DebugValidate) ValidateCode(param map[string]string) error { + code := param[model.ParamCode] + if v.mockCode == code { + return nil + } + if v.real == nil { + return errors.New("未注册验证器") + } + return v.real.ValidateCode(param) +} diff --git a/service/backup/service/debug/sms_test.go b/service/backup/service/debug/sms_test.go new file mode 100644 index 0000000..8796af2 --- /dev/null +++ b/service/backup/service/debug/sms_test.go @@ -0,0 +1,53 @@ +package debug + +import "testing" + +func TestGetMockCode(t *testing.T) { + type args struct { + mode string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "test 5 length", + args: args{ + mode: "FzmRandom5", + }, + want: "11111", + }, { + name: "test default length", + args: args{ + mode: "FzmRandom", + }, + want: "111111", + }, { + name: "test 0 length", + args: args{ + mode: "FzmRandom0", + }, + want: "111111", + }, { + name: "test 6 length", + args: args{ + mode: "FzmRandom6", + }, + want: "111111", + }, { + name: "test 4 length", + args: args{ + mode: "FzmRandom4", + }, + want: "1111", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := GetMockCode(tt.args.mode); got != tt.want { + t.Errorf("GetMockCode() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/service/backup/service/email/email.go b/service/backup/service/email/email.go new file mode 100644 index 0000000..fb600b4 --- /dev/null +++ b/service/backup/service/email/email.go @@ -0,0 +1,266 @@ +package email + +import ( + "crypto/md5" + "encoding/hex" + "encoding/json" + "fmt" + "gitlab.33.cn/chat/dtalk/service/backup/model" + "io/ioutil" + "net/http" + "net/url" + "sort" + "strconv" + "strings" + "time" + + "github.com/inconshreveable/log15" + http_tools "gitlab.33.cn/chat/dtalk/pkg/net/http" + . "gitlab.33.cn/chat/dtalk/service/backup/service/email/model" +) + +type Email struct { + appKey string + secretKey string + serviceUrl string + msg string +} + +func NewEmail(url, appKey, secretKey, msg string) *Email { + return &Email{ + appKey: appKey, + secretKey: secretKey, + serviceUrl: url, + msg: msg, + } +} + +func (e *Email) Send(param map[string]string) (interface{}, error) { + email := param[model.ParamEmail] + ticket := param[model.ParamTicket] + businessId := param[model.ParamBizId] + codeType := param[model.ParamCodeType] + + values := map[string]string{ + "email": email, + "codetype": codeType, + "param": e.msg, + "ticket": ticket, + "businessId": businessId, + } + + timestamp := strconv.FormatInt(time.Now().Unix(), 10) + reqMethod := "POST" + reqUrl := e.serviceUrl + "/send/email2" + strParams := MapToSortUrlEncode(values) + + sign := sginature(e.appKey, values, e.secretKey, timestamp) + + headerMap := map[string]string{ + "Content-Type": "application/x-www-form-urlencoded", + "FZM-Ca-Timestamp": timestamp, + "FZM-Ca-AppKey": e.appKey, + "FZM-Ca-Signature": sign, + } + + req, err := http.NewRequest(reqMethod, reqUrl, strings.NewReader(strParams)) + if err != nil { + return nil, err + } + + for k, v := range headerMap { + req.Header.Add(k, v) + } + + c := http.Client{ + Timeout: HttpReqTimeout, + } + + resp, err := c.Do(req) + if resp != nil { + defer func() { + err := resp.Body.Close() + if err != nil { + } + }() + } + + if err != nil { + return nil, err + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + var tresult interface{} + err = json.Unmarshal(body, &tresult) + if nil != err { + return nil, err + } + + result, ok := tresult.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("invaild tresult") + } + + sCode, err := http_tools.ParseInterface(result["code"], "string") + if nil != err { + return nil, err + } + + sError, err := http_tools.ParseInterface(result["error"], "string") + if nil != err { + return nil, err + } + + sMessage, err := http_tools.ParseInterface(result["message"], "string") + if nil != err { + return nil, err + } + + if "200" != sCode.(string) || "succ" != sError.(string) || "succ" != sMessage.(string) { + //return fmt.Errorf("code : " + sCode.(string) + ", error : " + sError.(string) + ", message : " + sMessage.(string)) + return nil, &Error{Code: sCode.(string), Err: sError.(string), Message: sMessage.(string)} + } + + data, ok := result["data"] + if !ok { + return nil, fmt.Errorf("no 'data' info") + } + + info := data.(map[string]interface{}) + log15.Debug("send result", "data", info) + isShow := int(info["isShow"].(float64)) + isValidate := int(info["isValidate"].(float64)) + + var rltData map[string]interface{} + if rltData, ok = info["data"].(map[string]interface{}); ok { + } + + return &SendResult{ + IsShow: isShow, + IsValidate: isValidate, + Data: rltData, + }, nil +} + +func (e *Email) ValidateCode(param map[string]string) error { + email := param[model.ParamEmail] + code := param[model.ParamCode] + codeType := param[model.ParamCodeType] + + values := map[string]string{ + "t": "email", + "codetype": codeType, + "code": code, + "guide": "0", + "email": email, + } + + timestamp := strconv.FormatInt(time.Now().Unix(), 10) + reqMethod := "POST" + reqUrl := e.serviceUrl + "/validate/code" + strParams := MapToSortUrlEncode(values) + + sign := sginature(e.appKey, values, e.secretKey, timestamp) + + headerMap := map[string]string{ + "Content-Type": "application/x-www-form-urlencoded", + "FZM-Ca-Timestamp": timestamp, + "FZM-Ca-AppKey": e.appKey, + "FZM-Ca-Signature": sign, + } + + req, err := http.NewRequest(reqMethod, reqUrl, strings.NewReader(strParams)) + if err != nil { + return err + } + + for k, v := range headerMap { + req.Header.Add(k, v) + } + + c := http.Client{ + Timeout: HttpReqTimeout, + } + + resp, err := c.Do(req) + if resp != nil { + defer func() { + err := resp.Body.Close() + if err != nil { + } + }() + } + + if err != nil { + return err + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + + var tresult interface{} + err = json.Unmarshal(body, &tresult) + if nil != err { + return err + } + + result, ok := tresult.(map[string]interface{}) + if !ok { + return fmt.Errorf("invaild tresult") + } + + sCode, err := http_tools.ParseInterface(result["code"], "string") + if nil != err { + return err + } + + sError, err := http_tools.ParseInterface(result["error"], "string") + if nil != err { + return err + } + + sMessage, err := http_tools.ParseInterface(result["message"], "string") + if nil != err { + return err + } + + if "200" != sCode.(string) || "succ" != sError.(string) || "succ" != sMessage.(string) { + //return fmt.Errorf("code : " + sCode.(string) + ", error : " + sError.(string) + ", message : " + sMessage.(string)) + return &Error{Code: sCode.(string), Err: sError.(string), Message: sMessage.(string)} + } + + return nil +} + +func sginature(appKey string, req map[string]string, secretKey string, time string) string { + signParams := MapToSortUrlEncode(req) + signParams = appKey + signParams + secretKey + time + h := md5.New() + h.Write([]byte(signParams)) + cipgerStr := h.Sum(nil) + sign := strings.ToUpper(hex.EncodeToString(cipgerStr)) + + return sign +} + +func MapToSortUrlEncode(paramsMap map[string]string) string { + v := url.Values{} + + mapKeys := []string{} + for k, _ := range paramsMap { + mapKeys = append(mapKeys, k) + } + sort.Strings(mapKeys) + + for k := range mapKeys { + v.Add(mapKeys[k], paramsMap[mapKeys[k]]) + } + body := v.Encode() + return body +} diff --git a/service/backup/service/email/email_test.go b/service/backup/service/email/email_test.go new file mode 100644 index 0000000..a277ee3 --- /dev/null +++ b/service/backup/service/email/email_test.go @@ -0,0 +1,51 @@ +package email + +import ( + "gitlab.33.cn/chat/dtalk/service/backup/model" + "testing" +) + +/* +[smsConfig] +url="http:///send/sms2" +codeType="chat_notice" +mobile=[""] +*/ +func Test_Send(t *testing.T) { + url := "http://118.31.52.32" + appkey := "chat33pro" + secretKey := "eQXXMphNFHQL4YeW" + msg := "FzmRandom5" + + email := NewEmail(url, appkey, secretKey, msg) + params := map[string]string{ + model.ParamEmail: "815904261@qq.com", + model.ParamCodeType: "bind_policebook", + } + rlt, err := email.Send(params) + if err != nil { + t.Error(err) + return + } + t.Log("success", "rlt", rlt) +} + +func Test_ValidateCode(t *testing.T) { + url := "http://118.31.52.32" + appkey := "chat33pro" + secretKey := "eQXXMphNFHQL4YeW" + msg := "FzmRandom5" + + email := NewEmail(url, appkey, secretKey, msg) + params := map[string]string{ + model.ParamEmail: "815904261@qq.com", + model.ParamCode: "17091", + model.ParamCodeType: "bind_policebook", + } + err := email.ValidateCode(params) + if err != nil { + t.Error(err) + return + } + t.Log("success") +} diff --git a/service/backup/service/email/model/const.go b/service/backup/service/email/model/const.go new file mode 100644 index 0000000..59ae609 --- /dev/null +++ b/service/backup/service/email/model/const.go @@ -0,0 +1,5 @@ +package model + +import "time" + +const HttpReqTimeout = 20 * time.Second diff --git a/service/backup/service/email/model/types.go b/service/backup/service/email/model/types.go new file mode 100644 index 0000000..8d8298f --- /dev/null +++ b/service/backup/service/email/model/types.go @@ -0,0 +1,17 @@ +package model + +type SendResult struct { + IsShow int + IsValidate int + Data map[string]interface{} +} + +type Error struct { + Message string + Err string + Code string +} + +func (e *Error) Error() string { + return e.Message +} diff --git a/service/backup/service/move.go b/service/backup/service/move.go new file mode 100644 index 0000000..edaa8a8 --- /dev/null +++ b/service/backup/service/move.go @@ -0,0 +1,27 @@ +package service + +import ( + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/service/backup/model" +) + +func (s *Service) AddressEnrolment(btyAddr, btcAddr string) error { + record, err := s.dao.QueryAddressEnrolment(btyAddr) + if err != nil { + return xerror.NewError(xerror.ExecFailed) + } + if record == nil { + err := s.dao.CreateAddressEnrolment(&model.AddrMove{ + BtyAddr: btyAddr, + BtcAddr: btcAddr, + State: 0, + }) + if err != nil { + return xerror.NewError(xerror.ExecFailed) + } + } + if record.BtcAddr != btcAddr { + return xerror.NewError(xerror.ExecFailed).SetExtMessage("new address is not consistent") + } + return nil +} diff --git a/service/backup/service/service.go b/service/backup/service/service.go new file mode 100644 index 0000000..bad1c1d --- /dev/null +++ b/service/backup/service/service.go @@ -0,0 +1,55 @@ +package service + +import ( + "github.com/inconshreveable/log15" + "gitlab.33.cn/chat/dtalk/service/backup/config" + "gitlab.33.cn/chat/dtalk/service/backup/dao" + "gitlab.33.cn/chat/dtalk/service/backup/model" + "gitlab.33.cn/chat/dtalk/service/backup/service/debug" + "gitlab.33.cn/chat/dtalk/service/backup/service/email" + "gitlab.33.cn/chat/dtalk/service/backup/service/sms" + "gitlab.33.cn/chat/dtalk/service/backup/service/whitelist" +) + +type Service struct { + log log15.Logger + cfg *config.Config + dao *dao.Dao + smsValidate model.Validate + emailValidate model.Validate +} + +func New(cfg *config.Config) *Service { + s := &Service{ + log: log15.New("module", "backup/svc"), + cfg: cfg, + dao: dao.New(cfg), + } + //SMS + { + var final model.Validate + final = sms.NewSMS(cfg.SMS.Surl, cfg.SMS.AppKey, cfg.SMS.SecretKey, cfg.SMS.Msg) + if cfg.SMS.Env == model.Debug { + final = debug.NewDebugValidate(debug.GetMockCode(cfg.SMS.Msg), final) + } + s.smsValidate = whitelist.NewWhitelistValidate(cfg.Whitelist, final) + } + //email + { + var final model.Validate + final = email.NewEmail(cfg.Email.Surl, cfg.Email.AppKey, cfg.Email.SecretKey, cfg.Email.Msg) + if cfg.Email.Env == model.Debug { + final = debug.NewDebugValidate(debug.GetMockCode(cfg.Email.Msg), final) + } + s.emailValidate = whitelist.NewWhitelistValidate(cfg.Whitelist, final) + } + return s +} + +func (s *Service) Ping() error { + return nil +} + +func (s Service) Config() *config.Config { + return s.cfg +} diff --git a/service/backup/service/service_test.go b/service/backup/service/service_test.go new file mode 100644 index 0000000..32f5c39 --- /dev/null +++ b/service/backup/service/service_test.go @@ -0,0 +1,58 @@ +package service + +import ( + "os" + "testing" + + "github.com/inconshreveable/log15" + "gitlab.33.cn/chat/dtalk/service/backup/config" + "gitlab.33.cn/chat/dtalk/service/backup/dao" + "gitlab.33.cn/chat/dtalk/service/backup/model" + "gitlab.33.cn/chat/dtalk/service/backup/service/debug" + "gitlab.33.cn/chat/dtalk/service/backup/service/email" + "gitlab.33.cn/chat/dtalk/service/backup/service/sms" +) + +var ( + testLog log15.Logger + testCfg *config.Config + testDao *dao.Dao + testSmsValidate model.Validate + testEmailValidate model.Validate +) + +func TestMain(m *testing.M) { + cfg := &config.Config{ + Server: &config.HttpServer{ + Addr: "0.0.0.0:18004", + }, + Env: "debug", + MySQL: &config.MySQL{ + Host: "127.0.0.1", + Port: 3306, + User: "root", + Pwd: "123456", + Db: "dtalk", + }, + SMS: config.SMS{ + Surl: "", + AppKey: "", + SecretKey: "", + Msg: "", + Env: "", + }, + Email: config.Email{ + Surl: "", + AppKey: "", + SecretKey: "", + Msg: "", + Env: "", + }, + } + testLog = log15.New("test", "backup/svc") + testCfg = cfg + testDao = dao.New(cfg) + testSmsValidate = debug.NewDebugValidate("111111", sms.NewSMS(cfg.SMS.Surl, cfg.SMS.AppKey, cfg.SMS.SecretKey, cfg.SMS.Msg)) + testEmailValidate = debug.NewDebugValidate("111111", email.NewEmail(cfg.Email.Surl, cfg.Email.AppKey, cfg.Email.SecretKey, cfg.Email.Msg)) + os.Exit(m.Run()) +} diff --git a/service/backup/service/sms/model/const.go b/service/backup/service/sms/model/const.go new file mode 100644 index 0000000..59ae609 --- /dev/null +++ b/service/backup/service/sms/model/const.go @@ -0,0 +1,5 @@ +package model + +import "time" + +const HttpReqTimeout = 20 * time.Second diff --git a/service/backup/service/sms/model/types.go b/service/backup/service/sms/model/types.go new file mode 100644 index 0000000..8d8298f --- /dev/null +++ b/service/backup/service/sms/model/types.go @@ -0,0 +1,17 @@ +package model + +type SendResult struct { + IsShow int + IsValidate int + Data map[string]interface{} +} + +type Error struct { + Message string + Err string + Code string +} + +func (e *Error) Error() string { + return e.Message +} diff --git a/service/backup/service/sms/sms.go b/service/backup/service/sms/sms.go new file mode 100644 index 0000000..5bbbc7b --- /dev/null +++ b/service/backup/service/sms/sms.go @@ -0,0 +1,266 @@ +package sms + +import ( + "crypto/md5" + "encoding/hex" + "encoding/json" + "fmt" + "gitlab.33.cn/chat/dtalk/service/backup/model" + "io/ioutil" + "net/http" + "net/url" + "sort" + "strconv" + "strings" + "time" + + "github.com/inconshreveable/log15" + http_tools "gitlab.33.cn/chat/dtalk/pkg/net/http" + . "gitlab.33.cn/chat/dtalk/service/backup/service/sms/model" +) + +type SMS struct { + appKey string + secretKey string + serviceUrl string + msg string +} + +func NewSMS(url, appKey, secretKey, msg string) *SMS { + return &SMS{ + serviceUrl: url, + appKey: appKey, + secretKey: secretKey, + msg: msg, + } +} + +func (s *SMS) Send(param map[string]string) (interface{}, error) { + phone := param[model.ParamMobile] + ticket := param[model.ParamTicket] + businessId := param[model.ParamBizId] + codeType := param[model.ParamCodeType] + + values := map[string]string{ + "mobile": phone, + "codetype": codeType, + "param": s.msg, + "ticket": ticket, + "businessId": businessId, + } + + timestamp := strconv.FormatInt(time.Now().Unix(), 10) + reqMethod := "POST" + reqUrl := s.serviceUrl + "/send/sms2" + strParams := MapToSortUrlEncode(values) + + sign := sginature(s.appKey, values, s.secretKey, timestamp) + + headerMap := map[string]string{ + "Content-Type": "application/x-www-form-urlencoded", + "FZM-Ca-Timestamp": timestamp, + "FZM-Ca-AppKey": s.appKey, + "FZM-Ca-Signature": sign, + } + + req, err := http.NewRequest(reqMethod, reqUrl, strings.NewReader(strParams)) + if err != nil { + return nil, err + } + + for k, v := range headerMap { + req.Header.Add(k, v) + } + + c := http.Client{ + Timeout: HttpReqTimeout, + } + + resp, err := c.Do(req) + if resp != nil { + defer func() { + err := resp.Body.Close() + if err != nil { + } + }() + } + + if err != nil { + return nil, err + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + var tresult interface{} + err = json.Unmarshal(body, &tresult) + if nil != err { + return nil, err + } + + result, ok := tresult.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("invaild tresult") + } + + sCode, err := http_tools.ParseInterface(result["code"], "string") + if nil != err { + return nil, err + } + + sError, err := http_tools.ParseInterface(result["error"], "string") + if nil != err { + return nil, err + } + + sMessage, err := http_tools.ParseInterface(result["message"], "string") + if nil != err { + return nil, err + } + + if "200" != sCode.(string) || "succ" != sError.(string) || "succ" != sMessage.(string) { + //return fmt.Errorf("code : " + sCode.(string) + ", error : " + sError.(string) + ", message : " + sMessage.(string)) + return nil, &Error{Code: sCode.(string), Err: sError.(string), Message: sMessage.(string)} + } + + data, ok := result["data"] + if !ok { + return nil, fmt.Errorf("no 'data' info") + } + + info := data.(map[string]interface{}) + log15.Debug("send result", "data", info) + isShow := int(info["isShow"].(float64)) + isValidate := int(info["isValidate"].(float64)) + + var rltData map[string]interface{} + if rltData, ok = info["data"].(map[string]interface{}); ok { + } + + return &SendResult{ + IsShow: isShow, + IsValidate: isValidate, + Data: rltData, + }, nil +} + +func (s *SMS) ValidateCode(param map[string]string) error { + phone := param[model.ParamMobile] + code := param[model.ParamCode] + codeType := param[model.ParamCodeType] + + values := map[string]string{ + "t": "sms", + "codetype": codeType, + "code": code, + "guide": "0", + "mobile": phone, + } + + timestamp := strconv.FormatInt(time.Now().Unix(), 10) + reqMethod := "POST" + reqUrl := s.serviceUrl + "/validate/code" + strParams := MapToSortUrlEncode(values) + + sign := sginature(s.appKey, values, s.secretKey, timestamp) + + headerMap := map[string]string{ + "Content-Type": "application/x-www-form-urlencoded", + "FZM-Ca-Timestamp": timestamp, + "FZM-Ca-AppKey": s.appKey, + "FZM-Ca-Signature": sign, + } + + req, err := http.NewRequest(reqMethod, reqUrl, strings.NewReader(strParams)) + if err != nil { + return err + } + + for k, v := range headerMap { + req.Header.Add(k, v) + } + + c := http.Client{ + Timeout: HttpReqTimeout, + } + + resp, err := c.Do(req) + if resp != nil { + defer func() { + err := resp.Body.Close() + if err != nil { + } + }() + } + + if err != nil { + return err + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + + var tresult interface{} + err = json.Unmarshal(body, &tresult) + if nil != err { + return err + } + + result, ok := tresult.(map[string]interface{}) + if !ok { + return fmt.Errorf("invaild tresult") + } + + sCode, err := http_tools.ParseInterface(result["code"], "string") + if nil != err { + return err + } + + sError, err := http_tools.ParseInterface(result["error"], "string") + if nil != err { + return err + } + + sMessage, err := http_tools.ParseInterface(result["message"], "string") + if nil != err { + return err + } + + if "200" != sCode.(string) || "succ" != sError.(string) || "succ" != sMessage.(string) { + //return fmt.Errorf("code : " + sCode.(string) + ", error : " + sError.(string) + ", message : " + sMessage.(string)) + return &Error{Code: sCode.(string), Err: sError.(string), Message: sMessage.(string)} + } + + return nil +} + +func sginature(appKey string, req map[string]string, secretKey string, time string) string { + signParams := MapToSortUrlEncode(req) + signParams = appKey + signParams + secretKey + time + h := md5.New() + h.Write([]byte(signParams)) + cipgerStr := h.Sum(nil) + sign := strings.ToUpper(hex.EncodeToString(cipgerStr)) + + return sign +} + +func MapToSortUrlEncode(paramsMap map[string]string) string { + v := url.Values{} + + mapKeys := []string{} + for k, _ := range paramsMap { + mapKeys = append(mapKeys, k) + } + sort.Strings(mapKeys) + + for k := range mapKeys { + v.Add(mapKeys[k], paramsMap[mapKeys[k]]) + } + body := v.Encode() + return body +} diff --git a/service/backup/service/sms/sms_test.go b/service/backup/service/sms/sms_test.go new file mode 100644 index 0000000..a77b223 --- /dev/null +++ b/service/backup/service/sms/sms_test.go @@ -0,0 +1,51 @@ +package sms + +import ( + "gitlab.33.cn/chat/dtalk/service/backup/model" + "testing" +) + +/* +[smsConfig] +url="http:///send/sms2" +codeType="chat_notice" +mobile=[""] +*/ +func Test_Send(t *testing.T) { + url := "http://118.31.52.32" + appkey := "chat33pro" + secretKey := "eQXXMphNFHQL4YeW" + msg := "FzmRandom5" + + sms := NewSMS(url, appkey, secretKey, msg) + params := map[string]string{ + model.ParamMobile: "15763946517", + model.ParamCodeType: "bind_policebook", + } + rlt, err := sms.Send(params) + if err != nil { + t.Error(err) + return + } + t.Log("success", "rlt", rlt) +} + +func Test_ValidateCode(t *testing.T) { + url := "http://118.31.52.32" + appkey := "chat33pro" + secretKey := "eQXXMphNFHQL4YeW" + msg := "FzmRandom5" + + sms := NewSMS(url, appkey, secretKey, msg) + params := map[string]string{ + model.ParamMobile: "15763946517", + model.ParamCode: "04037", + model.ParamCodeType: "bind_policebook", + } + err := sms.ValidateCode(params) + if err != nil { + t.Error(err) + return + } + t.Log("success") +} diff --git a/service/backup/service/whitelist/whitelist.go b/service/backup/service/whitelist/whitelist.go new file mode 100644 index 0000000..314e0bb --- /dev/null +++ b/service/backup/service/whitelist/whitelist.go @@ -0,0 +1,49 @@ +package whitelist + +import ( + "errors" + "gitlab.33.cn/chat/dtalk/service/backup/config" + "gitlab.33.cn/chat/dtalk/service/backup/model" +) + +type WhitelistValidate struct { + whitelist map[string]string + real model.Validate +} + +func NewWhitelistValidate(list []config.SMSEmailWhitelist, v model.Validate) *WhitelistValidate { + whitelist := make(map[string]string) + for _, item := range list { + if item.Enable { + whitelist[item.Account] = item.Code + } + } + return &WhitelistValidate{ + whitelist: whitelist, + real: v, + } +} + +func (v *WhitelistValidate) Send(params map[string]string) (interface{}, error) { + if v.real == nil { + return nil, errors.New("未注册验证器") + } + return v.real.Send(params) +} + +func (v *WhitelistValidate) ValidateCode(param map[string]string) error { + code := param[model.ParamCode] + phone := param[model.ParamMobile] + email := param[model.ParamEmail] + acc := phone + if phone == "" { + acc = email + } + if c, ok := v.whitelist[acc]; ok && c == code { + return nil + } + if v.real == nil { + return errors.New("未注册验证器") + } + return v.real.ValidateCode(param) +} diff --git a/service/backup/version.go b/service/backup/version.go new file mode 100644 index 0000000..e36545c --- /dev/null +++ b/service/backup/version.go @@ -0,0 +1,16 @@ +package version + +//var ( +// // The full version string +// Version = "1.0.18" +// +// // GitCommit is set with --ldflags "-X main.gitCommit=$(git rev-parse --short=8 HEAD)" +// GitCommit string +//) +// +//func GetVersion() string { +// if GitCommit != "" { +// return Version + "-" + GitCommit +// } +// return Version +//} diff --git a/service/call/CHANGELOG.md b/service/call/CHANGELOG.md new file mode 100644 index 0000000..e7f86ce --- /dev/null +++ b/service/call/CHANGELOG.md @@ -0,0 +1,45 @@ +版本号`major.minor.patch`具体规则如下: +- major:主版本号,如有重大版本重构则该字段递增,通常各主版本间接口不兼容。 +- minor:次版本号,各次版本号间接口保持兼容,如有接口新增或优化则该字段递增。 +- patch:补丁号,如有功能改善或缺陷修复则该字段递增。 + +## version 0.1.5 + +**配置文件更新** +- 所有 `[xxxRPCClient]` 增加 `RegAddrs = "127.0.0.1:2379"` 字段 2021_11_12 + +## version 0.1 + +**Feature** +- 接口增加 string 类型 Id @v0.1.0 2021.8.20 +- 接口可以选择只传整形或者字符串类型 ID @v0.1.2 2021.8.26 +- 改用 imparse 中的 proto @v0.1.3 2021.10.14 +- 更新 etcdv3.5.0 v0.1.4 + +**Bug Fix** +- string 类型转化为整形时增加error判断 @v0.1.1 2021.8.20 + +## version 0.0.8 @2021.7.19 + +**Feature** + +- 从服务端获取 userSign, privateMapKey @0.0.6 +- 从服务端获取 appid @0.0.7 +- roomId支持简单分布式 @0.0.8 + +## version 0.0.5 @2021.7.15 + +**Feature** + +- 通过 start-call, reply-busy, check-call, stop-call 四个接口实现私聊双方进入房间 + + +## example x.x.x @yy.mm.dd + +**Feature** + +**Bug Fixes** + +**Improvement** + +**Breaking Change** diff --git a/service/call/Makefile b/service/call/Makefile new file mode 100644 index 0000000..7e86d89 --- /dev/null +++ b/service/call/Makefile @@ -0,0 +1,48 @@ +# golang1.9 or latest +# 1. make help +# 2. make dep +# 3. make build +# ... + +VERSION := $(shell echo $(shell cat cmd/main.go | grep "projectVersion =" | cut -d '=' -f2)) +APP_NAME := call +BUILD_DIR := build +APP := ${BUILD_DIR}/${APP_NAME}_v${VERSION} +PKG_NAME := ${APP_NAME}_v${VERSION} +PKG := ${PKG_NAME}.tar.gz + +main_path = "main" +go_version = $(shell go version | awk '{ print $3 }') +build_time = $(shell date "+%Y-%m-%d %H:%M:%S %Z") +git_commit = $(shell git rev-parse --short=10 HEAD) +flags := -ldflags "-X '${main_path}.goVersion=${go_version}' \ +-X '${main_path}.buildTime=${build_time}' \ +-X '${main_path}.gitCommit=${git_commit}' \ +-X 'google.golang.org/protobuf/reflect/protoregistry.conflictPolicy=warn'" + +.PHONY: clean build pkg + +clean: ## Remove previous build + @rm -rf ${BUILD_DIR} + @go clean + +build: #checkgofmt ## Build the binary file + swag init -g server/http/http.go + GOOS=linux GOARCH=amd64 GO111MODULE=on GOPROXY=https://goproxy.cn,direct GOSUMDB="sum.golang.google.cn" go build -v $(flags) -o $(APP) cmd/main.go + +pkg: build ## Package + mkdir -p ${PKG_NAME}/bin + mkdir -p ${PKG_NAME}/etc + cp ${APP} ${PKG_NAME}/bin/ + cp config/$(APP_NAME).toml ${PKG_NAME}/etc/ + tar zvcf ${PKG} ${PKG_NAME} + rm -rf ${PKG_NAME} + +run: + swag init -g server/http/http.go + go run $(LDGRPC) $(LDFLAGS) cmd/main.go -conf config/call.toml + +REMOTE_BIN_PATH := /opt/dtalk/srv/app/bin +upload: build + rsync -r $(APP) 107:$(REMOTE_BIN_PATH) + ssh 107 "cd $(REMOTE_BIN_PATH) && chmod 777 $(PKG_NAME) && ln -sf $(PKG_NAME) $(APP_NAME) && supervisorctl restart $(APP_NAME)" \ No newline at end of file diff --git a/service/call/cmd/main.go b/service/call/cmd/main.go new file mode 100644 index 0000000..f407f9c --- /dev/null +++ b/service/call/cmd/main.go @@ -0,0 +1,83 @@ +package main + +import ( + "context" + "flag" + "fmt" + "os" + "os/signal" + "syscall" + "time" + + "gitlab.33.cn/chat/dtalk/pkg/logger" + "gitlab.33.cn/chat/dtalk/service/call/config" + "gitlab.33.cn/chat/dtalk/service/call/server/http" + "gitlab.33.cn/chat/dtalk/service/call/service" +) + +const srvName = "call" + +var ( + // projectVersion 项目版本 + projectVersion = "0.1.5" + // goVersion go版本 + goVersion = "" + // gitCommit git提交commit id + gitCommit = "" + // buildTime 编译时间 + buildTime = "" + + isShowVersion = flag.Bool("v", false, "show project version") +) + +// showVersion 显示项目版本信息 +func showVersion(isShow bool) { + if isShow { + fmt.Printf("Project: %s\n", srvName) + fmt.Printf(" Version: %s\n", projectVersion) + fmt.Printf(" Go Version: %s\n", goVersion) + fmt.Printf(" Git Commit: %s\n", gitCommit) + fmt.Printf(" Build Time: %s\n", buildTime) + os.Exit(0) + } +} + +func main() { + flag.Parse() + showVersion(*isShowVersion) + + if err := config.Init(); err != nil { + panic(err) + } + //log init + log := logger.New(config.Conf.Env, srvName) + log.Info().Interface("Config", config.Conf). + Msg("config info") + + // service init + svc := service.New(config.Conf) + httpSrv := http.Init(svc) + + // init signal + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT) + for { + s := <-c + log.Info().Str("signal", s.String()).Msg("service get a signal") + switch s { + case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT: + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := httpSrv.Shutdown(ctx); err != nil { + log.Error().Err(err).Msg("server shutdown") + } + time.Sleep(time.Second * 2) + log.Info().Msg(srvName + " server exit") + return + case syscall.SIGHUP: + // TODO reload + default: + return + } + } +} diff --git a/service/call/config/call.toml b/service/call/config/call.toml new file mode 100644 index 0000000..3696463 --- /dev/null +++ b/service/call/config/call.toml @@ -0,0 +1,38 @@ +Env = "debug" +AppId = "dtalk" +Node = 1 + +[HttpServer] +Addr = "0.0.0.0:18013" + +[Redis] +network = "tcp" +addr = "127.0.0.1:6379" +auth = "" +active = 60000 +idle = 1024 +dialTimeout = "200ms" +readTimeout = "500ms" +writeTimeout = "500ms" +idleTimeout = "120s" +expire = "30m" + +[IdGenRPCClient] +RegAddrs = "127.0.0.1:2379" +Schema = "dtalk" +SrvName = "generator" +Dial = "1s" +Timeout = "1s" + +[AnswerRPCClient] +RegAddrs = "127.0.0.1:2379" +Schema = "dtalk" +SrvName = "answer" +Dial = "1s" +Timeout = "1s" + +[TCRTCConfig] +SDKAppId = 1400543084 +SecretKey = "1ed1b5e2729395c1e8b55b83be72e60139dfa36ff3fa67bc3c3285592a7b3cf6" +Expire = 86400 + diff --git a/service/call/config/config.go b/service/call/config/config.go new file mode 100644 index 0000000..72121bd --- /dev/null +++ b/service/call/config/config.go @@ -0,0 +1,92 @@ +package config + +import ( + "flag" + "github.com/BurntSushi/toml" + "gitlab.33.cn/chat/dtalk/pkg/redis" + xtime "gitlab.33.cn/chat/dtalk/pkg/time" + "time" +) + +var ( + confPath string + + Conf *Config +) + +func init() { + flag.StringVar(&confPath, "conf", "call.toml", "default config path.") +} + +// Init init config. +func Init() (err error) { + Conf = Default() + _, err = toml.DecodeFile(confPath, &Conf) + return +} + +func Default() *Config { + return &Config{ + HttpServer: &HttpServer{ + Addr: "0.0.0.0:18013", + }, + Redis: &redis.Config{ + Network: "tcp", + Addr: "127.0.0.1:6379", + Auth: "", + Active: 60000, + Idle: 1024, + DialTimeout: xtime.Duration(200 * time.Millisecond), + ReadTimeout: xtime.Duration(500 * time.Millisecond), + WriteTimeout: xtime.Duration(500 * time.Millisecond), + IdleTimeout: xtime.Duration(120 * time.Second), + Expire: xtime.Duration(30 * time.Minute), + }, + IdGenRPCClient: &RPCClient{ + RegAddrs: "127.0.0.1:2379", + Schema: "dtalk", + SrvName: "generator", + Dial: xtime.Duration(time.Second), + Timeout: xtime.Duration(time.Second), + }, + AnswerRPCClient: &RPCClient{ + RegAddrs: "127.0.0.1:2379", + Schema: "dtalk", + SrvName: "answer", + Dial: xtime.Duration(time.Second), + Timeout: xtime.Duration(time.Second), + }, + } +} + +type Config struct { + Env string + AppId string + Node int32 + HttpServer *HttpServer + Redis *redis.Config + IdGenRPCClient *RPCClient + AnswerRPCClient *RPCClient + //gRPC server + TCRTCConfig *RTCConfig +} + +type HttpServer struct { + Addr string +} + +// RPCClient is RPC client config. +type RPCClient struct { + RegAddrs string // etcd addrs, seperate by ',' + Schema string + SrvName string // call + Dial xtime.Duration + Timeout xtime.Duration +} + +// RTCConfig 腾讯云音视频控制台配置 +type RTCConfig struct { + SDKAppId int + SecretKey string + Expire int +} diff --git a/service/call/dao/dao.go b/service/call/dao/dao.go new file mode 100644 index 0000000..e65686e --- /dev/null +++ b/service/call/dao/dao.go @@ -0,0 +1,24 @@ +package dao + +import ( + "github.com/rs/zerolog" + "gitlab.33.cn/chat/dtalk/pkg/logger" + "gitlab.33.cn/chat/dtalk/pkg/redis" + "gitlab.33.cn/chat/dtalk/service/call/config" +) + +var srvName = "call/dao" + +type Dao struct { + log zerolog.Logger + redis *redis.Pool +} + +func New(cfg *config.Config) *Dao { + d := &Dao{ + log: logger.New(cfg.Env, srvName), + redis: redis.New(cfg.Redis), + } + + return d +} diff --git a/service/call/dao/redis.go b/service/call/dao/redis.go new file mode 100644 index 0000000..3e23a7b --- /dev/null +++ b/service/call/dao/redis.go @@ -0,0 +1,75 @@ +package dao + +import ( + "fmt" + "gitlab.33.cn/chat/dtalk/service/call/model" +) + +const ( + _prefixCallSession = "session:%d" + _prefixCallStatus = "status:%s" +) + +func keySession(traceId int64) string { + return fmt.Sprintf(_prefixCallSession, traceId) +} + +func keyStatus(userId string) string { + return fmt.Sprintf(_prefixCallStatus, userId) +} + +// GetSession get session from traceId +func (d *Dao) GetSession(traceId int64) (*model.Session, error) { + key := keySession(traceId) + if ok, err := d.redis.Exists(key); err != nil { + return nil, err + } else if !ok { + return nil, model.ErrSessionNotExist + } + + res := &model.Session{} + if err := d.redis.Read(key, res); err != nil { + return nil, err + } + + return res, nil +} + +func (d *Dao) SaveSession(session *model.Session) error { + key := keySession(session.TraceId) + if err := d.redis.Write(key, session, model.SESSIONMAXTIME); err != nil { + return err + } + return nil +} + +// 弃用 +func (d *Dao) GetStatus(userId string) (int, error) { + d.redis.RLock() + defer d.redis.RUnlock() + + key := keyStatus(userId) + if ok, err := d.redis.Exists(key); err != nil { + return 0, err + } else if !ok { + return 0, nil + } + + exist, err := d.redis.GetInt(key) + if err != nil { + return 0, err + } + return exist, nil +} + +// 弃用 +func (d *Dao) SetStatus(userId string, isBusy int) error { + d.redis.Lock() + defer d.redis.Unlock() + + key := keyStatus(userId) + if err := d.redis.Set(key, isBusy); err != nil { + return err + } + return nil +} diff --git a/service/call/docs/docs.go b/service/call/docs/docs.go new file mode 100644 index 0000000..1087eac --- /dev/null +++ b/service/call/docs/docs.go @@ -0,0 +1,421 @@ +// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT +// This file was generated by swaggo/swag + +package docs + +import ( + "bytes" + "encoding/json" + "strings" + + "github.com/alecthomas/template" + "github.com/swaggo/swag" +) + +var doc = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{.Description}}", + "title": "{{.Title}}", + "contact": {}, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/app/check-call": { + "post": { + "tags": [ + "call" + ], + "summary": "检查通话状态", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/model.CheckCallRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/model.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.CheckCallResponse" + } + } + } + ] + } + } + } + } + }, + "/app/handle-call": { + "post": { + "tags": [ + "call" + ], + "summary": "处理通话", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/model.HandleCallRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/model.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.HandleCallResponse" + } + } + } + ] + } + } + } + } + }, + "/app/reply-busy": { + "post": { + "tags": [ + "call" + ], + "summary": "返回忙碌", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/model.ReplyBusyRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/model.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.ReplyBusyResponse" + } + } + } + ] + } + } + } + } + }, + "/app/start-call": { + "post": { + "tags": [ + "call" + ], + "summary": "开始通话", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/model.StartCallRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/model.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.StartCallResponse" + } + } + } + ] + } + } + } + } + } + }, + "definitions": { + "model.CheckCallRequest": { + "type": "object", + "properties": { + "traceId": { + "type": "integer" + }, + "traceIdStr": { + "description": "如果同时填了 tracedIdStr, 则优先选择 traceIdStr", + "type": "string" + } + } + }, + "model.CheckCallResponse": { + "type": "object", + "properties": { + "RTCType": { + "type": "integer" + }, + "caller": { + "type": "string" + }, + "createTime": { + "type": "integer" + }, + "deadline": { + "type": "integer" + }, + "groupId": { + "type": "string" + }, + "invitees": { + "type": "array", + "items": { + "type": "string" + } + }, + "timeout": { + "type": "integer" + }, + "traceId": { + "type": "integer" + }, + "traceIdStr": { + "type": "string" + } + } + }, + "model.GeneralResponse": { + "type": "object", + "properties": { + "data": { + "type": "object" + }, + "message": { + "type": "integer" + }, + "result": { + "type": "integer" + } + } + }, + "model.HandleCallRequest": { + "type": "object", + "properties": { + "answer": { + "type": "boolean" + }, + "traceId": { + "type": "integer" + }, + "traceIdStr": { + "description": "如果同时填了 tracedIdStr, 则优先选择 traceIdStr", + "type": "string" + } + } + }, + "model.HandleCallResponse": { + "type": "object", + "properties": { + "privateMapKey": { + "type": "string" + }, + "roomId": { + "type": "integer" + }, + "sdkAppId": { + "type": "integer" + }, + "userSig": { + "type": "string" + } + } + }, + "model.ReplyBusyRequest": { + "type": "object", + "properties": { + "traceId": { + "type": "integer" + }, + "traceIdStr": { + "description": "如果同时填了 tracedIdStr, 则优先选择 traceIdStr", + "type": "string" + } + } + }, + "model.ReplyBusyResponse": { + "type": "object" + }, + "model.StartCallRequest": { + "type": "object", + "required": [ + "invitees" + ], + "properties": { + "RTCType": { + "type": "integer" + }, + "groupId": { + "type": "string" + }, + "invitees": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "model.StartCallResponse": { + "type": "object", + "properties": { + "RTCType": { + "type": "integer" + }, + "caller": { + "type": "string" + }, + "createTime": { + "type": "integer" + }, + "deadline": { + "type": "integer" + }, + "groupId": { + "description": "0表示私聊, 其他表示群聊", + "type": "string" + }, + "invitees": { + "type": "array", + "items": { + "type": "string" + } + }, + "timeout": { + "type": "integer" + }, + "traceId": { + "type": "integer" + }, + "traceIdStr": { + "type": "string" + } + } + } + } +}` + +type swaggerInfo struct { + Version string + Host string + BasePath string + Schemes []string + Title string + Description string +} + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = swaggerInfo{ + Version: "1.0", + Host: "127.0.0.1:18013", + BasePath: "", + Schemes: []string{}, + Title: "音视频信令服务接口", + Description: "", +} + +type s struct{} + +func (s *s) ReadDoc() string { + sInfo := SwaggerInfo + sInfo.Description = strings.Replace(sInfo.Description, "\n", "\\n", -1) + + t, err := template.New("swagger_info").Funcs(template.FuncMap{ + "marshal": func(v interface{}) string { + a, _ := json.Marshal(v) + return string(a) + }, + }).Parse(doc) + if err != nil { + return doc + } + + var tpl bytes.Buffer + if err := t.Execute(&tpl, sInfo); err != nil { + return doc + } + + return tpl.String() +} + +func init() { + swag.Register(swag.Name, &s{}) +} diff --git a/service/call/docs/swagger.json b/service/call/docs/swagger.json new file mode 100644 index 0000000..ef0538e --- /dev/null +++ b/service/call/docs/swagger.json @@ -0,0 +1,357 @@ +{ + "swagger": "2.0", + "info": { + "title": "音视频信令服务接口", + "contact": {}, + "version": "1.0" + }, + "host": "127.0.0.1:18013", + "paths": { + "/app/check-call": { + "post": { + "tags": [ + "call" + ], + "summary": "检查通话状态", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/model.CheckCallRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/model.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.CheckCallResponse" + } + } + } + ] + } + } + } + } + }, + "/app/handle-call": { + "post": { + "tags": [ + "call" + ], + "summary": "处理通话", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/model.HandleCallRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/model.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.HandleCallResponse" + } + } + } + ] + } + } + } + } + }, + "/app/reply-busy": { + "post": { + "tags": [ + "call" + ], + "summary": "返回忙碌", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/model.ReplyBusyRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/model.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.ReplyBusyResponse" + } + } + } + ] + } + } + } + } + }, + "/app/start-call": { + "post": { + "tags": [ + "call" + ], + "summary": "开始通话", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "schema": { + "$ref": "#/definitions/model.StartCallRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/model.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.StartCallResponse" + } + } + } + ] + } + } + } + } + } + }, + "definitions": { + "model.CheckCallRequest": { + "type": "object", + "properties": { + "traceId": { + "type": "integer" + }, + "traceIdStr": { + "description": "如果同时填了 tracedIdStr, 则优先选择 traceIdStr", + "type": "string" + } + } + }, + "model.CheckCallResponse": { + "type": "object", + "properties": { + "RTCType": { + "type": "integer" + }, + "caller": { + "type": "string" + }, + "createTime": { + "type": "integer" + }, + "deadline": { + "type": "integer" + }, + "groupId": { + "type": "string" + }, + "invitees": { + "type": "array", + "items": { + "type": "string" + } + }, + "timeout": { + "type": "integer" + }, + "traceId": { + "type": "integer" + }, + "traceIdStr": { + "type": "string" + } + } + }, + "model.GeneralResponse": { + "type": "object", + "properties": { + "data": { + "type": "object" + }, + "message": { + "type": "integer" + }, + "result": { + "type": "integer" + } + } + }, + "model.HandleCallRequest": { + "type": "object", + "properties": { + "answer": { + "type": "boolean" + }, + "traceId": { + "type": "integer" + }, + "traceIdStr": { + "description": "如果同时填了 tracedIdStr, 则优先选择 traceIdStr", + "type": "string" + } + } + }, + "model.HandleCallResponse": { + "type": "object", + "properties": { + "privateMapKey": { + "type": "string" + }, + "roomId": { + "type": "integer" + }, + "sdkAppId": { + "type": "integer" + }, + "userSig": { + "type": "string" + } + } + }, + "model.ReplyBusyRequest": { + "type": "object", + "properties": { + "traceId": { + "type": "integer" + }, + "traceIdStr": { + "description": "如果同时填了 tracedIdStr, 则优先选择 traceIdStr", + "type": "string" + } + } + }, + "model.ReplyBusyResponse": { + "type": "object" + }, + "model.StartCallRequest": { + "type": "object", + "required": [ + "invitees" + ], + "properties": { + "RTCType": { + "type": "integer" + }, + "groupId": { + "type": "string" + }, + "invitees": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "model.StartCallResponse": { + "type": "object", + "properties": { + "RTCType": { + "type": "integer" + }, + "caller": { + "type": "string" + }, + "createTime": { + "type": "integer" + }, + "deadline": { + "type": "integer" + }, + "groupId": { + "description": "0表示私聊, 其他表示群聊", + "type": "string" + }, + "invitees": { + "type": "array", + "items": { + "type": "string" + } + }, + "timeout": { + "type": "integer" + }, + "traceId": { + "type": "integer" + }, + "traceIdStr": { + "type": "string" + } + } + } + } +} \ No newline at end of file diff --git a/service/call/docs/swagger.yaml b/service/call/docs/swagger.yaml new file mode 100644 index 0000000..bb8f471 --- /dev/null +++ b/service/call/docs/swagger.yaml @@ -0,0 +1,220 @@ +definitions: + model.CheckCallRequest: + properties: + traceId: + type: integer + traceIdStr: + description: 如果同时填了 tracedIdStr, 则优先选择 traceIdStr + type: string + type: object + model.CheckCallResponse: + properties: + RTCType: + type: integer + caller: + type: string + createTime: + type: integer + deadline: + type: integer + groupId: + type: string + invitees: + items: + type: string + type: array + timeout: + type: integer + traceId: + type: integer + traceIdStr: + type: string + type: object + model.GeneralResponse: + properties: + data: + type: object + message: + type: integer + result: + type: integer + type: object + model.HandleCallRequest: + properties: + answer: + type: boolean + traceId: + type: integer + traceIdStr: + description: 如果同时填了 tracedIdStr, 则优先选择 traceIdStr + type: string + type: object + model.HandleCallResponse: + properties: + privateMapKey: + type: string + roomId: + type: integer + sdkAppId: + type: integer + userSig: + type: string + type: object + model.ReplyBusyRequest: + properties: + traceId: + type: integer + traceIdStr: + description: 如果同时填了 tracedIdStr, 则优先选择 traceIdStr + type: string + type: object + model.ReplyBusyResponse: + type: object + model.StartCallRequest: + properties: + RTCType: + type: integer + groupId: + type: string + invitees: + items: + type: string + type: array + required: + - invitees + type: object + model.StartCallResponse: + properties: + RTCType: + type: integer + caller: + type: string + createTime: + type: integer + deadline: + type: integer + groupId: + description: 0表示私聊, 其他表示群聊 + type: string + invitees: + items: + type: string + type: array + timeout: + type: integer + traceId: + type: integer + traceIdStr: + type: string + type: object +host: 127.0.0.1:18013 +info: + contact: {} + title: 音视频信令服务接口 + version: "1.0" +paths: + /app/check-call: + post: + parameters: + - description: MOCK + in: header + name: FZM-SIGNATURE + required: true + type: string + - description: body + in: body + name: data + schema: + $ref: '#/definitions/model.CheckCallRequest' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/model.GeneralResponse' + - properties: + data: + $ref: '#/definitions/model.CheckCallResponse' + type: object + summary: 检查通话状态 + tags: + - call + /app/handle-call: + post: + parameters: + - description: MOCK + in: header + name: FZM-SIGNATURE + required: true + type: string + - description: body + in: body + name: data + schema: + $ref: '#/definitions/model.HandleCallRequest' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/model.GeneralResponse' + - properties: + data: + $ref: '#/definitions/model.HandleCallResponse' + type: object + summary: 处理通话 + tags: + - call + /app/reply-busy: + post: + parameters: + - description: MOCK + in: header + name: FZM-SIGNATURE + required: true + type: string + - description: body + in: body + name: data + schema: + $ref: '#/definitions/model.ReplyBusyRequest' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/model.GeneralResponse' + - properties: + data: + $ref: '#/definitions/model.ReplyBusyResponse' + type: object + summary: 返回忙碌 + tags: + - call + /app/start-call: + post: + parameters: + - description: MOCK + in: header + name: FZM-SIGNATURE + required: true + type: string + - description: body + in: body + name: data + schema: + $ref: '#/definitions/model.StartCallRequest' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/model.GeneralResponse' + - properties: + data: + $ref: '#/definitions/model.StartCallResponse' + type: object + summary: 开始通话 + tags: + - call +swagger: "2.0" diff --git a/service/call/model/const.go b/service/call/model/const.go new file mode 100644 index 0000000..127ccec --- /dev/null +++ b/service/call/model/const.go @@ -0,0 +1,27 @@ +package model + +const ( + // READYTIME 拨号最长持续时间 + READYTIME = 60 + BUSY = true + FREE = false + + READY = 0 + INPROGRESS = 1 + FINISH = 2 + + MAXROOMID = 1000000000 + + // SESSIONMAXTIME session 在 redis 中过期的时间 + SESSIONMAXTIME = 60 * 60 * 24 +) + +type StopType int32 + +const ( + Busy StopType = 0 + Timeout StopType = 1 + Reject StopType = 2 + Hangup StopType = 3 + Cancel StopType = 4 +) diff --git a/service/call/model/error.go b/service/call/model/error.go new file mode 100644 index 0000000..f0d79bf --- /dev/null +++ b/service/call/model/error.go @@ -0,0 +1,11 @@ +package model + +import ( + "github.com/pkg/errors" +) + +var ( + ErrSessionNotExist = errors.New("the session is not exist") + ErrUserBusy = errors.New("user is busy") + ErrFeaturesUnSupported = errors.New("Features UnSupported") +) diff --git a/service/call/model/http.go b/service/call/model/http.go new file mode 100644 index 0000000..981e5ce --- /dev/null +++ b/service/call/model/http.go @@ -0,0 +1,71 @@ +package model + +type GeneralResponse struct { + Result int `json:"result"` + Message int `json:"message"` + Data interface{} `json:"data"` +} + +type StartCallRequest struct { + PersonId string `json:"-"` + GroupId string `json:"groupId"` + Invitees []string `json:"invitees" binding:"required"` + RTCType int32 `json:"RTCType" binding:"oneof=1 2"` +} + +type StartCallResponse struct { + TraceId int64 `json:"traceId"` + TraceIdStr string `json:"traceIdStr"` + RTCType int32 `json:"RTCType"` + Invitees []string `json:"invitees"` + Caller string `json:"caller"` + CreateTime int64 `json:"createTime"` + Timeout int32 `json:"timeout"` + Deadline int64 `json:"deadline"` + // 0表示私聊, 其他表示群聊 + GroupId string `json:"groupId"` +} + +type ReplyBusyRequest struct { + PersonId string `json:"-"` + TraceId int64 `json:"traceId"` + // 如果同时填了 tracedIdStr, 则优先选择 traceIdStr + TraceIdStr string `json:"traceIdStr"` +} + +type ReplyBusyResponse struct { +} + +type CheckCallRequest struct { + PersonId string `json:"-"` + TraceId int64 `json:"traceId"` + // 如果同时填了 tracedIdStr, 则优先选择 traceIdStr + TraceIdStr string `json:"traceIdStr"` +} + +type CheckCallResponse struct { + TraceId int64 `json:"traceId"` + TraceIdStr string `json:"traceIdStr"` + RTCType int32 `json:"RTCType"` + Invitees []string `json:"invitees"` + Caller string `json:"caller"` + CreateTime int64 `json:"createTime"` + Timeout int32 `json:"timeout"` + Deadline int64 `json:"deadline"` + GroupId string `json:"groupId"` +} + +type HandleCallRequest struct { + PersonId string `json:"-"` + Answer bool `json:"answer"` + TraceId int64 `json:"traceId"` + // 如果同时填了 tracedIdStr, 则优先选择 traceIdStr + TraceIdStr string `json:"traceIdStr"` +} + +type HandleCallResponse struct { + RoomId int32 `json:"roomId"` + UserSig string `json:"userSig"` + PrivateMapKey string `json:"privateMapKey"` + SDKAppId int32 `json:"sdkAppId"` +} diff --git a/service/call/model/room.go b/service/call/model/room.go new file mode 100644 index 0000000..110df97 --- /dev/null +++ b/service/call/model/room.go @@ -0,0 +1,32 @@ +package model + +import "sync" + +// Room roomId 生成器, 获得临时唯一的 roomId +type Room struct { + // TODO 换个更科学的 + id int32 + max int32 + node int32 + sync.Mutex +} + +func NewRoom(node int32) *Room { + if node > 9 { + node = 9 + } + room := &Room{ + id: 0, + max: 1000000, + } + room.node = node * room.max + return room +} + +// GetID 递增 1 获得 roomId, 到达 max 后取模归零 +func (r *Room) GetID() int32 { + r.Lock() + defer r.Unlock() + r.id = (r.id + 1) % r.max + return r.node + r.id +} diff --git a/service/call/model/session.go b/service/call/model/session.go new file mode 100644 index 0000000..8cdb6a3 --- /dev/null +++ b/service/call/model/session.go @@ -0,0 +1,43 @@ +package model + +import ( + idgen "gitlab.33.cn/chat/dtalk/service/generator/api" + "time" +) + +type Session struct { + TraceId int64 + RTCType int32 + RoomId int32 + // 超出 Deadline 对方未接就结束通话 + Deadline int64 + // 0=对方未接通, 1=双方正在通话中, 2=通话结束 + Status int32 + Invitees []string + Caller string + Timeout int32 + CreateTime int64 + GroupId int64 +} + +func NewSession(RTCType int32, caller string, invitees []string, groupId int64, + idgenClient *idgen.Client, room *Room) (*Session, error) { + traceId, err := idgenClient.GetID() + if err != nil { + return nil, err + } + roomId := room.GetID() + session := &Session{ + RTCType: RTCType, + TraceId: traceId, + RoomId: roomId, + Timeout: READYTIME * 1000, + Deadline: time.Now().Add(READYTIME*time.Second).UnixNano() / 1e6, + Status: READY, + Invitees: invitees, + Caller: caller, + CreateTime: time.Now().UnixNano() / 1e6, + GroupId: groupId, + } + return session, nil +} diff --git a/service/call/model/user.go b/service/call/model/user.go new file mode 100644 index 0000000..2d39245 --- /dev/null +++ b/service/call/model/user.go @@ -0,0 +1,39 @@ +package model + +import "sync" + +// 弃用 +type User struct { + status map[string]bool + sync.RWMutex +} + +func NewUser() *User { + user := &User{ + status: make(map[string]bool), + } + return user +} + +func (u *User) GetStatus(key string) bool { + u.RLock() + defer u.RUnlock() + val, ok := u.status[key] + if !ok { + return false + } + return val +} + +func (u *User) SetStatus(key string, isBusy bool) error { + u.Lock() + defer u.Unlock() + + preStatus := u.status[key] + if preStatus == BUSY && isBusy == BUSY { + return ErrUserBusy + } + + u.status[key] = isBusy + return nil +} diff --git a/service/call/server/http/checkCall.go b/service/call/server/http/checkCall.go new file mode 100644 index 0000000..0d7a764 --- /dev/null +++ b/service/call/server/http/checkCall.go @@ -0,0 +1,52 @@ +package http + +import ( + "github.com/gin-gonic/gin" + "gitlab.33.cn/chat/dtalk/pkg/api" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/pkg/util" + "gitlab.33.cn/chat/dtalk/service/call/model" +) + +// checkCall +// @Summary 检查通话状态 +// @Author chy@33.cn +// @Tags call +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body model.CheckCallRequest false "body" +// @Success 200 {object} model.GeneralResponse{data=model.CheckCallResponse} +// @Router /app/check-call [post] +func checkCall(c *gin.Context) { + userId, ok := c.Get(api.Address) + if !ok { + c.Set(api.ReqError, xerror.NewError(xerror.SignatureInvalid)) + return + } + + req := &model.CheckCallRequest{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + if req.TraceId == 0 && req.TraceIdStr == "" { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.TraceIdStr != "" { + traceId, err := util.ToInt64E(req.TraceIdStr) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + req.TraceId = traceId + } + + req.PersonId = userId.(string) + + res, err := svc.CheckCall(req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) +} diff --git a/service/call/server/http/handleCall.go b/service/call/server/http/handleCall.go new file mode 100644 index 0000000..6f57fa2 --- /dev/null +++ b/service/call/server/http/handleCall.go @@ -0,0 +1,52 @@ +package http + +import ( + "github.com/gin-gonic/gin" + "gitlab.33.cn/chat/dtalk/pkg/api" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/pkg/util" + "gitlab.33.cn/chat/dtalk/service/call/model" +) + +// handleCall +// @Summary 处理通话 +// @Author chy@33.cn +// @Tags call +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body model.HandleCallRequest false "body" +// @Success 200 {object} model.GeneralResponse{data=model.HandleCallResponse} +// @Router /app/handle-call [post] +func handleCall(c *gin.Context) { + userId, ok := c.Get(api.Address) + if !ok { + c.Set(api.ReqError, xerror.NewError(xerror.SignatureInvalid)) + return + } + + req := &model.HandleCallRequest{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + if req.TraceId == 0 && req.TraceIdStr == "" { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.TraceIdStr != "" { + traceId, err := util.ToInt64E(req.TraceIdStr) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + req.TraceId = traceId + } + + req.PersonId = userId.(string) + + res, err := svc.HandleCall(req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) +} diff --git a/service/call/server/http/http.go b/service/call/server/http/http.go new file mode 100644 index 0000000..3d6447f --- /dev/null +++ b/service/call/server/http/http.go @@ -0,0 +1,73 @@ +package http + +import ( + "github.com/gin-gonic/gin" + "github.com/rs/zerolog" + "gitlab.33.cn/chat/dtalk/pkg/api" + "gitlab.33.cn/chat/dtalk/pkg/logger" + "gitlab.33.cn/chat/dtalk/service/call/service" + "net/http" + + swaggerFiles "github.com/swaggo/files" + ginSwagger "github.com/swaggo/gin-swagger" + _ "gitlab.33.cn/chat/dtalk/service/call/docs" +) + +const srvName = "call/http" + +var ( + svc *service.Service + log zerolog.Logger +) + +func Init(s *service.Service) *http.Server { + addr := s.Config().HttpServer.Addr + engine := defaultEngine() + initService(s) + setupEngine(engine) + log = logger.New(s.Config().Env, srvName) + + srv := &http.Server{ + Addr: addr, + Handler: engine, + } + go func() { + // service connections + if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Error().Err(err).Msg("engineInner.Start()") + panic(err) + } + }() + return srv +} + +// defaultEngine returns an Engine instance with the Logger and Recovery middleware already attached. +func defaultEngine() *gin.Engine { + router := gin.New() + // LoggerWithFormatter middleware will write the logs to gin.DefaultWriter + // By default gin.DefaultWriter = os.Stdout + router.Use(gin.LoggerWithFormatter(api.Chat33GinLogFormatter)) + router.Use(gin.Recovery()) + return router +} + +func initService(s *service.Service) { + svc = s +} + +// setupEngine +// @title 音视频信令服务接口 +// @version 1.0 +// @host 127.0.0.1:18013 +func setupEngine(e *gin.Engine) *gin.Engine { + app := e.Group("/app", api.RespMiddleWare(), api.AuthMiddleWare()) + { + app.POST("/start-call", startCall) + app.POST("/reply-busy", replyBusy) + app.POST("/check-call", checkCall) + app.POST("/handle-call", handleCall) + } + + e.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) + return e +} diff --git a/service/call/server/http/replyBusy.go b/service/call/server/http/replyBusy.go new file mode 100644 index 0000000..35adf73 --- /dev/null +++ b/service/call/server/http/replyBusy.go @@ -0,0 +1,52 @@ +package http + +import ( + "github.com/gin-gonic/gin" + "gitlab.33.cn/chat/dtalk/pkg/api" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/pkg/util" + "gitlab.33.cn/chat/dtalk/service/call/model" +) + +// replyBusy +// @Summary 返回忙碌 +// @Author chy@33.cn +// @Tags call +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body model.ReplyBusyRequest false "body" +// @Success 200 {object} model.GeneralResponse{data=model.ReplyBusyResponse} +// @Router /app/reply-busy [post] +func replyBusy(c *gin.Context) { + userId, ok := c.Get(api.Address) + if !ok { + c.Set(api.ReqError, xerror.NewError(xerror.SignatureInvalid)) + return + } + + req := &model.ReplyBusyRequest{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + if req.TraceId == 0 && req.TraceIdStr == "" { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.TraceIdStr != "" { + traceId, err := util.ToInt64E(req.TraceIdStr) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + req.TraceId = traceId + } + + req.PersonId = userId.(string) + + res, err := svc.ReplyBusy(req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) +} diff --git a/service/call/server/http/startCall.go b/service/call/server/http/startCall.go new file mode 100644 index 0000000..9a553e2 --- /dev/null +++ b/service/call/server/http/startCall.go @@ -0,0 +1,37 @@ +package http + +import ( + "github.com/gin-gonic/gin" + "gitlab.33.cn/chat/dtalk/pkg/api" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/service/call/model" +) + +// startCall +// @Summary 开始通话 +// @Author chy@33.cn +// @Tags call +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body model.StartCallRequest false "body" +// @Success 200 {object} model.GeneralResponse{data=model.StartCallResponse} +// @Router /app/start-call [post] +func startCall(c *gin.Context) { + userId, ok := c.Get(api.Address) + if !ok { + c.Set(api.ReqError, xerror.NewError(xerror.SignatureInvalid)) + return + } + + req := &model.StartCallRequest{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + req.PersonId = userId.(string) + + res, err := svc.StartCall(req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) +} diff --git a/service/call/service/answerClient.go b/service/call/service/answerClient.go new file mode 100644 index 0000000..f370c03 --- /dev/null +++ b/service/call/service/answerClient.go @@ -0,0 +1,66 @@ +package service + +import ( + "context" + "github.com/golang/protobuf/proto" + "github.com/pkg/errors" + "gitlab.33.cn/chat/dtalk/service/call/model" + xproto "gitlab.33.cn/chat/imparse/proto" +) + +func (s *Service) noticeStartCall(target string, traceId int64) error { + action := &xproto.SignalStartCall{ + TraceId: traceId, + } + body, err := proto.Marshal(action) + if err != nil { + return errors.WithMessagef(err, "proto.Marshal, action=%+v", action) + } + + return s.answerClient.UniCastSignal(context.Background(), xproto.SignalType_StartCall, target, body) +} + +func (s *Service) noticeAcceptCall(target string, traceId int64, roomId int32, userSig, privateMapKey string, sdkAppId int32) error { + action := &xproto.SignalAcceptCall{ + TraceId: traceId, + RoomId: roomId, + Uid: target, + UserSig: userSig, + PrivateMapKey: privateMapKey, + SkdAppId: sdkAppId, + } + body, err := proto.Marshal(action) + if err != nil { + return errors.WithMessagef(err, "proto.Marshal, action=%+v", action) + } + + return s.answerClient.UniCastSignal(context.Background(), xproto.SignalType_AcceptCall, target, body) +} + +func (s *Service) noticeStopCall(Target string, TraceId int64, stopType model.StopType) error { + var StopCallType xproto.StopCallType + switch xproto.StopCallType(stopType) { + case xproto.StopCallType_Busy: + StopCallType = xproto.StopCallType_Busy + case xproto.StopCallType_Timeout: + StopCallType = xproto.StopCallType_Timeout + case xproto.StopCallType_Reject: + StopCallType = xproto.StopCallType_Reject + case xproto.StopCallType_Hangup: + StopCallType = xproto.StopCallType_Hangup + case xproto.StopCallType_Cancel: + StopCallType = xproto.StopCallType_Cancel + + } + action := &xproto.SignalStopCall{ + TraceId: TraceId, + Reason: StopCallType, + } + + body, err := proto.Marshal(action) + if err != nil { + return errors.WithMessagef(err, "proto.Marshal, action=%+v", action) + } + + return s.answerClient.UniCastSignal(context.Background(), xproto.SignalType_StopCall, Target, body) +} diff --git a/service/call/service/checkCall.go b/service/call/service/checkCall.go new file mode 100644 index 0000000..006c91d --- /dev/null +++ b/service/call/service/checkCall.go @@ -0,0 +1,57 @@ +package service + +import ( + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/pkg/util" + "gitlab.33.cn/chat/dtalk/service/call/model" + "time" +) + +func (s *Service) CheckCall(req *model.CheckCallRequest) (res *model.CheckCallResponse, err error) { + defer func() { + if err != nil { + s.log.Error().Err(err).Interface("req", req).Str("personId", req.PersonId).Msg("CheckCall") + } else { + s.log.Info().Interface("req", req).Str("personId", req.PersonId).Msg("CheckCall") + } + }() + + // 从 redis 中获得 session + session, err := s.dao.GetSession(req.TraceId) + if err != nil { + return nil, xerror.NewError(xerror.CodeInnerError).SetExtMessage(err.Error()) + } + + // TODO 暂不支持群会议 + if session.GroupId != 0 { + return nil, xerror.NewError(xerror.ParamsError) + } + + // 判断 session 是否过期 + nowTime := time.Now().UnixNano() / 1e6 + if session.Deadline < nowTime { + return nil, xerror.NewError(xerror.CodeInnerError).SetExtMessage("1") + } + + // TODO 判断是否在被接收方组内 + //if session.Target != req.PersonId { + // return nil, xerror.NewError(xerror.CodeInnerError).SetExtMessage("2") + //} + + // 判断 session 状态是否在准备中 + if session.Status != model.READY { + return nil, xerror.NewError(xerror.CodeInnerError).SetExtMessage("3") + } + + return &model.CheckCallResponse{ + TraceId: session.TraceId, + TraceIdStr: util.ToString(session.TraceId), + RTCType: session.RTCType, + Invitees: session.Invitees, + Caller: session.Caller, + CreateTime: session.CreateTime, + Deadline: session.Deadline, + GroupId: util.ToString(session.GroupId), + Timeout: session.Timeout, + }, nil +} diff --git a/service/call/service/handleCall.go b/service/call/service/handleCall.go new file mode 100644 index 0000000..53ad5a9 --- /dev/null +++ b/service/call/service/handleCall.go @@ -0,0 +1,138 @@ +package service + +import ( + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/service/call/model" +) + +func (s *Service) HandleCall(req *model.HandleCallRequest) (res *model.HandleCallResponse, err error) { + defer func() { + if err != nil { + s.log.Error().Err(err).Interface("req", req).Str("personId", req.PersonId).Msg("HandleCall") + } else { + s.log.Info().Interface("req", req).Str("personId", req.PersonId).Msg("HandleCall") + } + }() + + // 从 redis 中获得 session + session, err := s.dao.GetSession(req.TraceId) + if err != nil { + return nil, xerror.NewError(xerror.CodeInnerError).SetExtMessage(err.Error()) + } + + // A 或 B 拒绝 + if req.Answer == false { + if err := s.refuseCall(req.PersonId, session); err != nil { + return &model.HandleCallResponse{}, xerror.NewError(xerror.CodeInnerError).SetExtMessage(err.Error()) + } + return &model.HandleCallResponse{}, nil + } + + // B 接受 + res, err = s.acceptCall(session) + if err != nil { + return &model.HandleCallResponse{}, xerror.NewError(xerror.CodeInnerError).SetExtMessage(err.Error()) + } + return res, nil +} + +// refuseCall 拒绝通话流程 +func (s *Service) refuseCall(personId string, session *model.Session) error { + // TODO 暂不支持群会议 + if session.GroupId != 0 { + return model.ErrFeaturesUnSupported + } + + if session.Status == model.READY { + if personId == session.Caller { + // 发起方主动取消 + // 给 对方 发 ActionStopCall with Cancel + if err := s.noticeStopCall(session.Invitees[0], session.TraceId, model.Cancel); err != nil { + s.log.Error().Err(err).Msg("refuseCall() noticeStopCall() 1") + } + } else { + // 对方拒绝 + // 给 Caller 发 ActionStopCall with Reject + if err := s.noticeStopCall(session.Caller, session.TraceId, model.Reject); err != nil { + s.log.Error().Err(err).Msg("refuseCall() noticeStopCall() 2") + } + } + } else { + // 双方挂断 + // 给对方通知 ActionStopCall with Hangup + if personId == session.Caller { + if err := s.noticeStopCall(session.Invitees[0], session.TraceId, model.Hangup); err != nil { + s.log.Error().Err(err).Msg("refuseCall() noticeStopCall() 1") + } + } else { + if err := s.noticeStopCall(session.Caller, session.TraceId, model.Hangup); err != nil { + s.log.Error().Err(err).Msg("refuseCall() noticeStopCall() 2") + } + } + } + + session.Status = model.FINISH + err := s.dao.SaveSession(session) + if err != nil { + return err + } + + return nil +} + +// acceptCall 接受通话流程, 只有被接收方可进行该流程 +func (s *Service) acceptCall(session *model.Session) (*model.HandleCallResponse, error) { + // TODO 暂不支持群聊 + if session.GroupId != 0 { + return nil, model.ErrSessionNotExist + } + + // TODO 判断是否在被接收方组内 + + if session.Status != model.READY { + return nil, model.ErrSessionNotExist + } + + // 修改 session 状态并保存到 redis 中 + session.Status = model.INPROGRESS + err := s.dao.SaveSession(session) + if err != nil { + return nil, err + } + + // 获得 AppId + sdkAppId := s.tlsSig.GetAppId() + + // 生成接收方的 userSig 和 privateMapKey + userSigForInvitees, err := s.tlsSig.GetUserSig(session.Invitees[0]) + if err != nil { + return nil, err + } + privateMapKeyForInvitees, err := s.tlsSig.GenPrivateMapKey(session.Invitees[0], session.RoomId, 255) + if err != nil { + return nil, err + } + + // 生成发起方的 userSig 和 privateMapKey + userSigForCaller, err := s.tlsSig.GetUserSig(session.Caller) + if err != nil { + return nil, err + } + privateMapKeyForCaller, err := s.tlsSig.GenPrivateMapKey(session.Caller, session.RoomId, 255) + if err != nil { + return nil, err + } + + // 给Caller发 ActionAcceptCall + if err = s.noticeAcceptCall(session.Caller, session.TraceId, session.RoomId, userSigForCaller, privateMapKeyForCaller, sdkAppId); err != nil { + s.log.Error().Err(err).Msg("acceptCall() noticeAcceptCall()") + } + + res := &model.HandleCallResponse{ + RoomId: session.RoomId, + UserSig: userSigForInvitees, + PrivateMapKey: privateMapKeyForInvitees, + SDKAppId: sdkAppId, + } + return res, nil +} diff --git a/service/call/service/replyBusy.go b/service/call/service/replyBusy.go new file mode 100644 index 0000000..b53e072 --- /dev/null +++ b/service/call/service/replyBusy.go @@ -0,0 +1,34 @@ +package service + +import ( + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/service/call/model" +) + +func (s *Service) ReplyBusy(req *model.ReplyBusyRequest) (res *model.ReplyBusyResponse, err error) { + defer func() { + if err != nil { + s.log.Error().Err(err).Interface("req", req).Str("personId", req.PersonId).Msg("ReplyBusy") + } else { + s.log.Info().Interface("req", req).Str("personId", req.PersonId).Msg("ReplyBusy") + } + }() + + // 从 redis 中获得 session + session, err := s.dao.GetSession(req.TraceId) + if err != nil { + return nil, xerror.NewError(xerror.CodeInnerError).SetExtMessage(err.Error()) + } + + if session.GroupId == 0 { + // 给 Caller 发 ActionStopCall with Busy + if err := s.noticeStopCall(session.Caller, session.TraceId, model.Busy); err != nil { + s.log.Error().Err(err).Msg("ReplyBusy() noticeStopCall()") + } + } else { + // TODO 如果是群聊 + return nil, xerror.NewError(xerror.ParamsError) + } + + return &model.ReplyBusyResponse{}, nil +} diff --git a/service/call/service/service.go b/service/call/service/service.go new file mode 100644 index 0000000..c756223 --- /dev/null +++ b/service/call/service/service.go @@ -0,0 +1,49 @@ +package service + +import ( + "gitlab.33.cn/chat/dtalk/pkg/sign/tencentyun" + "gitlab.33.cn/chat/dtalk/service/call/model" + idgen "gitlab.33.cn/chat/dtalk/service/generator/api" + answer "gitlab.33.cn/chat/dtalk/service/record/answer/api" + "time" + + "github.com/rs/zerolog" + "gitlab.33.cn/chat/dtalk/pkg/logger" + "gitlab.33.cn/chat/dtalk/service/call/config" + "gitlab.33.cn/chat/dtalk/service/call/dao" +) + +type Service struct { + log zerolog.Logger + cfg *config.Config + dao *dao.Dao + + idGenRPCClient *idgen.Client + answerClient *answer.Client + + room *model.Room + tlsSig tencentyun.TLSSig +} + +const srvName = "call/srv" + +func New(cfg *config.Config) *Service { + s := &Service{ + log: logger.New(cfg.Env, srvName), + cfg: cfg, + dao: dao.New(cfg), + idGenRPCClient: idgen.New(cfg.IdGenRPCClient.RegAddrs, cfg.IdGenRPCClient.Schema, cfg.IdGenRPCClient.SrvName, time.Duration(cfg.IdGenRPCClient.Dial)), + answerClient: answer.New(cfg.AnswerRPCClient.RegAddrs, cfg.AnswerRPCClient.Schema, cfg.AnswerRPCClient.SrvName, time.Duration(cfg.AnswerRPCClient.Dial)), + room: model.NewRoom(cfg.Node), + tlsSig: tencentyun.NewTCTLSSig(cfg.TCRTCConfig.SDKAppId, cfg.TCRTCConfig.SecretKey, cfg.TCRTCConfig.Expire), + } + return s +} + +func (s *Service) Ping() error { + return nil +} + +func (s *Service) Config() *config.Config { + return s.cfg +} diff --git a/service/call/service/startCall.go b/service/call/service/startCall.go new file mode 100644 index 0000000..284c862 --- /dev/null +++ b/service/call/service/startCall.go @@ -0,0 +1,99 @@ +package service + +import ( + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/pkg/util" + "gitlab.33.cn/chat/dtalk/service/call/model" + "time" +) + +// StartCall 准备发起通话 +func (s *Service) StartCall(req *model.StartCallRequest) (res *model.StartCallResponse, err error) { + defer func() { + if err != nil { + s.log.Error().Err(err).Interface("req", req).Str("personId", req.PersonId).Msg("StartCall") + } else { + s.log.Info().Interface("req", req).Str("personId", req.PersonId).Msg("StartCall") + } + }() + + var groupId int64 + if req.GroupId != "" { + groupId, err = util.ToInt64E(req.GroupId) + if err != nil { + return nil, err + } + } + + // 如果是私聊,Invitees只能有一人 + if groupId == 0 && len(req.Invitees) != 1 { + return nil, xerror.NewError(xerror.ParamsError) + } + + // 生成 session + session, err := model.NewSession( + req.RTCType, req.PersonId, req.Invitees, + groupId, s.idGenRPCClient, s.room) + if err != nil { + return nil, xerror.NewError(xerror.CodeInnerError).SetExtMessage(err.Error()) + } + + // 保存在 redis 中 + if err = s.dao.SaveSession(session); err != nil { + return nil, xerror.NewError(xerror.CodeInnerError).SetExtMessage(err.Error()) + } + + if session.GroupId == 0 { + // 给 B 发 ActionStartCall + if err = s.noticeStartCall(session.Invitees[0], session.TraceId); err != nil { + s.log.Error().Err(err).Msg("StartCall() noticeStartCall()") + } + } else { + // TODO 如果是群聊 + return nil, xerror.NewError(xerror.ParamsError) + } + + // 给 Caller 返回 traceId + res = &model.StartCallResponse{ + RTCType: session.RTCType, + TraceId: session.TraceId, + TraceIdStr: util.ToString(session.TraceId), + Caller: session.Caller, + Invitees: session.Invitees, + CreateTime: session.CreateTime, + Deadline: session.Deadline, + GroupId: util.ToString(session.GroupId), + Timeout: session.Timeout, + } + return res, nil +} + +// checkTimeout 弃用 +func (s *Service) checkTimeout(traceId int64, readyTime int32) { + durationTime := time.Second * time.Duration(readyTime) + timeTickerChan := time.Tick(durationTime) + <-timeTickerChan + + session, err := s.dao.GetSession(traceId) + if err != nil { + s.log.Error().Err(err).Msg("checkTimeout GetSession") + } + if session.Status != model.READY { + return + } + if session.GroupId == 0 { + // 给双方发 ActionStopCall with Timeout + if err := s.noticeStopCall(session.Caller, session.TraceId, model.Timeout); err != nil { + s.log.Error().Err(err).Msg("StartCall() checkTimeout() noticeStopCall()") + } + if err := s.noticeStopCall(session.Invitees[0], session.TraceId, model.Timeout); err != nil { + s.log.Error().Err(err).Msg("StartCall() checkTimeout() noticeStopCall()") + } + + session.Status = model.FINISH + if err := s.dao.SaveSession(session); err != nil { + s.log.Error().Err(err).Msg("checkTimeout SaveSession") + } + } + +} diff --git a/service/call/version.go b/service/call/version.go new file mode 100644 index 0000000..b3cf180 --- /dev/null +++ b/service/call/version.go @@ -0,0 +1,16 @@ +package version + +//var ( +// // The full version string +// Version = "0.1.4" +// +// // GitCommit is set with --ldflags "-X main.gitCommit=$(git rev-parse --short=8 HEAD)" +// GitCommit string +//) +// +//func GetVersion() string { +// if GitCommit != "" { +// return Version + "-" + GitCommit +// } +// return Version +//} diff --git a/service/device/build/device_v0.0.1 b/service/device/build/device_v0.0.1 new file mode 100644 index 0000000..0ff005b Binary files /dev/null and b/service/device/build/device_v0.0.1 differ diff --git a/service/discovery/CHANGELOG.md b/service/discovery/CHANGELOG.md new file mode 100644 index 0000000..a696a48 --- /dev/null +++ b/service/discovery/CHANGELOG.md @@ -0,0 +1,19 @@ +版本号`major.minor.patch`具体规则如下: +- major:主版本号,如有重大版本重构则该字段递增,通常各主版本间接口不兼容。 +- minor:次版本号,各次版本号间接口保持兼容,如有接口新增或优化则该字段递增。 +- patch:补丁号,如有功能改善或缺陷修复则该字段递增。 + +## version 0.0.1 + +init discovery + + +## example x.x.x @yy.mm.dd + +**Feature** + +**Bug Fixes** + +**Improvement** + +**Breaking Change** diff --git a/service/discovery/Makefile b/service/discovery/Makefile new file mode 100644 index 0000000..8d693f7 --- /dev/null +++ b/service/discovery/Makefile @@ -0,0 +1,43 @@ +# golang1.9 or latest +# 1. make help +# 2. make dep +# 3. make build +# ... + +VERSION := $(shell echo $(shell cat cmd/main.go | grep "projectVersion =" | cut -d '=' -f2)) +APP_NAME := discovery +BUILD_DIR := build +APP := ${BUILD_DIR}/${APP_NAME}_v${VERSION} +PKG_NAME := ${APP_NAME}_v${VERSION} +PKG := ${PKG_NAME}.tar.gz + +main_path = "main" +go_version = $(shell go version | awk '{ print $3 }') +build_time = $(shell date "+%Y-%m-%d %H:%M:%S %Z") +git_commit = $(shell git rev-parse --short=10 HEAD) +flags := -ldflags "-X '${main_path}.goVersion=${go_version}' \ +-X '${main_path}.buildTime=${build_time}' \ +-X '${main_path}.gitCommit=${git_commit}' \ +-X 'google.golang.org/protobuf/reflect/protoregistry.conflictPolicy=warn'" + +.PHONY: clean build pkg + +clean: ## Remove previous build + @rm -rf ${BUILD_DIR} + @go clean + +build: #checkgofmt ## Build the binary file + GOOS=linux GOARCH=amd64 GO111MODULE=on GOPROXY=https://goproxy.cn,direct GOSUMDB="sum.golang.google.cn" go build -v $(flags) -o $(APP) cmd/main.go + +pkg: build ## Package + mkdir -p ${PKG_NAME}/bin + mkdir -p ${PKG_NAME}/etc + cp ${APP} ${PKG_NAME}/bin/ + cp etc/* ${PKG_NAME}/etc/ + tar zvcf ${PKG} ${PKG_NAME} + rm -rf ${PKG_NAME} + +REMOTE_BIN_PATH := /opt/dtalk/srv/app/bin +upload: build + rsync -r $(APP) 107:$(REMOTE_BIN_PATH) + ssh 107 "cd $(REMOTE_BIN_PATH) && chmod 777 $(PKG_NAME) && ln -sf $(PKG_NAME) $(APP_NAME) && supervisorctl restart disc" \ No newline at end of file diff --git a/service/discovery/cmd/main.go b/service/discovery/cmd/main.go new file mode 100644 index 0000000..29680df --- /dev/null +++ b/service/discovery/cmd/main.go @@ -0,0 +1,90 @@ +package main + +import ( + "context" + "flag" + "fmt" + "os" + "os/signal" + "syscall" + "time" + + "github.com/inconshreveable/log15" + "gitlab.33.cn/chat/dtalk/service/discovery/config" + "gitlab.33.cn/chat/dtalk/service/discovery/server/http" + "gitlab.33.cn/chat/dtalk/service/discovery/service" +) + +const srvName = "discovery" + +var log = log15.New("cmd", srvName) + +var ( + // projectVersion 项目版本 + projectVersion = "0.0.1" + // goVersion go版本 + goVersion = "" + // gitCommit git提交commit id + gitCommit = "" + // buildTime 编译时间 + buildTime = "" + + isShowVersion = flag.Bool("v", false, "show project version") +) + +// showVersion 显示项目版本信息 +func showVersion(isShow bool) { + if isShow { + fmt.Printf("Project: %s\n", srvName) + fmt.Printf(" Version: %s\n", projectVersion) + fmt.Printf(" Go Version: %s\n", goVersion) + fmt.Printf(" Git Commit: %s\n", gitCommit) + fmt.Printf(" Build Time: %s\n", buildTime) + os.Exit(0) + } +} + +// @Title 聊天单模块集成测试 +// @Version 0.1 +// @Description +// @SecurityDefinitions.ApiKey ApiKeyAuth +// @In header +// @Name Authorization +// @BasePath / +func main() { + flag.Parse() + showVersion(*isShowVersion) + + if err := config.Init(); err != nil { + panic(err) + } + log.Info("config info:", + "Redis", *config.Conf.Redis, + "Server", *config.Conf.Server) + // service init + svc := service.New(config.Conf) + httpSrv := http.Init(svc) + + // init signal + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT) + for { + s := <-c + log.Info("service get a signal %s", s.String()) + switch s { + case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT: + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := httpSrv.Shutdown(ctx); err != nil { + log.Error("server shutdown:", "err", err) + } + time.Sleep(time.Second * 2) + log.Info(srvName + " server exit") + return + case syscall.SIGHUP: + // TODO reload + default: + return + } + } +} diff --git a/service/discovery/config/config.go b/service/discovery/config/config.go new file mode 100644 index 0000000..e2f42b1 --- /dev/null +++ b/service/discovery/config/config.go @@ -0,0 +1,85 @@ +package config + +import ( + "flag" + "github.com/BurntSushi/toml" + xtime "gitlab.33.cn/chat/dtalk/pkg/time" + "gitlab.33.cn/chat/dtalk/service/discovery/model" + "time" +) + +var ( + confPath string + + Conf *Config +) + +func init() { + flag.StringVar(&confPath, "conf", "discovery.toml", "default config path.") +} + +// Init init config. +func Init() (err error) { + Conf = Default() + _, err = toml.DecodeFile(confPath, &Conf) + return +} + +func Default() *Config { + return &Config{ + Server: &HttpServer{ + Addr: "0.0.0.0:18001", + }, + + Redis: &Redis{ + Network: "tcp", + Addr: "127.0.0.1:6379", + Auth: "", + Active: 60000, + Idle: 1024, + DialTimeout: xtime.Duration(200 * time.Millisecond), + ReadTimeout: xtime.Duration(500 * time.Millisecond), + WriteTimeout: xtime.Duration(500 * time.Millisecond), + IdleTimeout: xtime.Duration(120 * time.Second), + Expire: xtime.Duration(30 * time.Minute), + }, + CNodes: []*model.CNode{ + { + Name: "聊天节点1", + Address: "", + }, + }, + DNodes: []*model.DNode{ + { + Name: "合约节点1", + Address: "", + }, + }, + } +} + +// Config config. +type Config struct { + Server *HttpServer + Redis *Redis + CNodes []*model.CNode + DNodes []*model.DNode +} + +type HttpServer struct { + Addr string +} + +// Redis . +type Redis struct { + Network string + Addr string + Auth string + Active int + Idle int + DialTimeout xtime.Duration + ReadTimeout xtime.Duration + WriteTimeout xtime.Duration + IdleTimeout xtime.Duration + Expire xtime.Duration +} diff --git a/service/discovery/config/discovery.toml b/service/discovery/config/discovery.toml new file mode 100644 index 0000000..0dc7c98 --- /dev/null +++ b/service/discovery/config/discovery.toml @@ -0,0 +1,22 @@ +[server] +addr="0.0.0.0:18001" + +[redis] +network="tcp" +addr="127.0.0.1:6379" +auth="" +active=60000 +idle=1024 +dialTimeout="200ms" +readTimeout="500ms" +writeTimeout="500ms" +idleTimeout="120s" +expire="30m" + +[[CNodes]] +name="" +address="" + +[[DNodes]] +name="" +address="" \ No newline at end of file diff --git a/service/discovery/dao/dao.go b/service/discovery/dao/dao.go new file mode 100644 index 0000000..bf47547 --- /dev/null +++ b/service/discovery/dao/dao.go @@ -0,0 +1,42 @@ +package dao + +import ( + "time" + + "github.com/gomodule/redigo/redis" + "github.com/inconshreveable/log15" + conf "gitlab.33.cn/chat/dtalk/service/discovery/config" +) + +type Dao struct { + log log15.Logger + redis *redis.Pool +} + +func New(c *conf.Config) *Dao { + d := &Dao{ + log: log15.New("module", "discovery/dao"), + redis: newRedis(c.Redis), + } + return d +} + +func newRedis(c *conf.Redis) *redis.Pool { + return &redis.Pool{ + MaxIdle: c.Idle, + MaxActive: c.Active, + IdleTimeout: time.Duration(c.IdleTimeout), + Dial: func() (redis.Conn, error) { + conn, err := redis.Dial(c.Network, c.Addr, + redis.DialConnectTimeout(time.Duration(c.DialTimeout)), + redis.DialReadTimeout(time.Duration(c.ReadTimeout)), + redis.DialWriteTimeout(time.Duration(c.WriteTimeout)), + redis.DialPassword(c.Auth), + ) + if err != nil { + return nil, err + } + return conn, nil + }, + } +} diff --git a/service/discovery/dao/redis.go b/service/discovery/dao/redis.go new file mode 100644 index 0000000..48755f1 --- /dev/null +++ b/service/discovery/dao/redis.go @@ -0,0 +1,99 @@ +package dao + +import ( + "encoding/json" + "fmt" + "github.com/gomodule/redigo/redis" + "gitlab.33.cn/chat/dtalk/service/discovery/model" +) + +const ( + cNode = "find:chat_node" + dNode = "find:chain33_node" +) + +//key:name; val:json +func (d *Dao) SetCNode(key string, node *model.CNode) error { + val, err := json.Marshal(node) + if err != nil { + return err + } + conn := d.redis.Get() + defer conn.Close() + if err := conn.Send("HSET", cNode, key, val); err != nil { + d.log.Error(fmt.Sprintf("conn.Send(HSET %s,%s,%s)", cNode, key, val), "err", err) + return err + } + if err := conn.Flush(); err != nil { + d.log.Error("conn.Flush()", "err", err) + return err + } + if _, err := conn.Receive(); err != nil { + d.log.Error("conn.Receive()", "err", err) + return err + } + return nil +} + +func (d *Dao) GetCNodes() ([]*model.CNode, error) { + conn := d.redis.Get() + defer conn.Close() + nMap, err := redis.StringMap(conn.Do("HGETALL", cNode)) + if err != nil { + d.log.Error(fmt.Sprintf("conn.DO(HGETALL %s)", cNode), "err", err) + return nil, err + } + nodes := make([]*model.CNode, 0) + for _, v := range nMap { + item := model.CNode{} + err := json.Unmarshal([]byte(v), &item) + if err != nil { + return nil, err + } + nodes = append(nodes, &item) + } + return nodes, nil +} + +//key:name; val:json +func (d *Dao) SetDNode(key string, node *model.DNode) error { + val, err := json.Marshal(node) + if err != nil { + return err + } + conn := d.redis.Get() + defer conn.Close() + if err := conn.Send("HSET", dNode, key, val); err != nil { + d.log.Error(fmt.Sprintf("conn.Send(HSET %s,%s,%s)", dNode, key, val), "err", err) + return err + } + if err := conn.Flush(); err != nil { + d.log.Error("conn.Flush()", "err", err) + return err + } + if _, err := conn.Receive(); err != nil { + d.log.Error("conn.Receive()", "err", err) + return err + } + return nil +} + +func (d *Dao) GetDNodes() ([]*model.DNode, error) { + conn := d.redis.Get() + defer conn.Close() + nMap, err := redis.StringMap(conn.Do("HGETALL", dNode)) + if err != nil { + d.log.Error(fmt.Sprintf("conn.DO(HGETALL %s)", dNode), "err", err) + return nil, err + } + nodes := make([]*model.DNode, 0) + for _, v := range nMap { + item := model.DNode{} + err := json.Unmarshal([]byte(v), &item) + if err != nil { + return nil, err + } + nodes = append(nodes, &item) + } + return nodes, nil +} diff --git a/service/discovery/dao/redis_test.go b/service/discovery/dao/redis_test.go new file mode 100644 index 0000000..7fb4e84 --- /dev/null +++ b/service/discovery/dao/redis_test.go @@ -0,0 +1,87 @@ +package dao + +import ( + "github.com/gomodule/redigo/redis" + "github.com/inconshreveable/log15" + xtime "gitlab.33.cn/chat/dtalk/pkg/time" + "gitlab.33.cn/chat/dtalk/service/discovery/config" + "gitlab.33.cn/chat/dtalk/service/discovery/model" + "os" + "testing" + "time" +) + +var test_redis_pool *redis.Pool + +func TestMain(m *testing.M) { + c := &config.Redis{ + Network: "tcp", + Addr: "172.24.143.30:6379", + Auth: "", + Active: 60000, + Idle: 1024, + DialTimeout: xtime.Duration(200 * time.Millisecond), + ReadTimeout: xtime.Duration(500 * time.Millisecond), + WriteTimeout: xtime.Duration(500 * time.Millisecond), + IdleTimeout: xtime.Duration(120 * time.Second), + Expire: xtime.Duration(30 * time.Minute), + } + + test_redis_pool = &redis.Pool{ + MaxIdle: c.Idle, + MaxActive: c.Active, + IdleTimeout: time.Duration(c.IdleTimeout), + Dial: func() (redis.Conn, error) { + conn, err := redis.Dial(c.Network, c.Addr, + redis.DialConnectTimeout(time.Duration(c.DialTimeout)), + redis.DialReadTimeout(time.Duration(c.ReadTimeout)), + redis.DialWriteTimeout(time.Duration(c.WriteTimeout)), + redis.DialPassword(c.Auth), + ) + if err != nil { + return nil, err + } + return conn, nil + }, + } + os.Exit(m.Run()) +} + +func TestDao_GetCNodes(t *testing.T) { + type fields struct { + log log15.Logger + redis *redis.Pool + } + tests := []struct { + name string + fields fields + want []*model.CNode + wantErr bool + }{ + { + name: "test get all", + fields: fields{ + log: log15.New(""), + redis: test_redis_pool, + }, + want: nil, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + d := &Dao{ + log: tt.fields.log, + redis: tt.fields.redis, + } + got, err := d.GetCNodes() + if (err != nil) != tt.wantErr { + t.Errorf("GetCNodes() error = %v, wantErr %v", err, tt.wantErr) + return + } + for i, node := range got { + t.Logf("item %v = %v", i, node) + } + }) + } +} diff --git a/service/discovery/model/node.go b/service/discovery/model/node.go new file mode 100644 index 0000000..3242599 --- /dev/null +++ b/service/discovery/model/node.go @@ -0,0 +1,13 @@ +package model + +//聊天服务器节点 +type CNode struct { + Name string `json:"name"` + Address string `json:"address"` +} + +//合约节点 +type DNode struct { + Name string `json:"name"` + Address string `json:"address"` +} diff --git a/service/discovery/server/http/http.go b/service/discovery/server/http/http.go new file mode 100644 index 0000000..b82d1ed --- /dev/null +++ b/service/discovery/server/http/http.go @@ -0,0 +1,58 @@ +package http + +import ( + "github.com/gin-gonic/gin" + "github.com/inconshreveable/log15" + "gitlab.33.cn/chat/dtalk/pkg/api" + "gitlab.33.cn/chat/dtalk/service/discovery/service" + "net/http" +) + +var ( + svc *service.Service + log = log15.New("module", "discovery/http") +) + +func Init(s *service.Service) *http.Server { + addr := s.Config().Server.Addr + engine := Default() + InitService(s) + SetupEngine(engine) + + srv := &http.Server{ + Addr: addr, + Handler: engine, + } + go func() { + // service connections + if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Error("engineInner.Start() error(%v)", err) + panic(err) + } + }() + return srv +} + +// Default returns an Engine instance with the Logger and Recovery middleware already attached. +func Default() *gin.Engine { + router := gin.New() + // LoggerWithFormatter middleware will write the logs to gin.DefaultWriter + // By default gin.DefaultWriter = os.Stdout + router.Use(gin.LoggerWithFormatter(api.Chat33GinLogFormatter)) + router.Use(gin.Recovery()) + return router +} + +func InitService(s *service.Service) { + svc = s +} + +func SetupEngine(e *gin.Engine) *gin.Engine { + //TODO 这边鉴权还是调用base + //inner := e.Group("/inner") + //inner.GET("/userInfo", UserInfo, api.RespMiddleWare()) + root := e.Group("/", api.RespMiddleWare()) + //获取服务器列表 + root.Any("/nodes", Nodes) + return e +} diff --git a/service/discovery/server/http/node.go b/service/discovery/server/http/node.go new file mode 100644 index 0000000..4faff82 --- /dev/null +++ b/service/discovery/server/http/node.go @@ -0,0 +1,26 @@ +package http + +import ( + "github.com/gin-gonic/gin" + "gitlab.33.cn/chat/dtalk/pkg/api" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" +) + +//get all nodes +func Nodes(c *gin.Context) { + cNodes, err := svc.CNodes() + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.QueryFailed)) + return + } + dNodes, err := svc.DNodes() + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.QueryFailed)) + return + } + ret := make(map[string]interface{}) + ret["servers"] = cNodes + ret["nodes"] = dNodes + c.Set(api.ReqResult, ret) + c.Set(api.ReqError, nil) +} diff --git a/service/discovery/service/node.go b/service/discovery/service/node.go new file mode 100644 index 0000000..a6e2346 --- /dev/null +++ b/service/discovery/service/node.go @@ -0,0 +1,28 @@ +package service + +import ( + "gitlab.33.cn/chat/dtalk/service/discovery/model" +) + +func (s *Service) StoreNodes(cNodes []*model.CNode, dNodes []*model.DNode) { + for i := 0; i < len(cNodes); i++ { + err := s.dao.SetCNode(cNodes[i].Name, cNodes[i]) + if err != nil { + s.log.Error("init chat node failed", "err", err) + } + } + for i := 0; i < len(dNodes); i++ { + err := s.dao.SetDNode(dNodes[i].Name, dNodes[i]) + if err != nil { + s.log.Error("init chain33 node failed", "err", err) + } + } +} + +func (s *Service) CNodes() ([]*model.CNode, error) { + return s.dao.GetCNodes() +} + +func (s *Service) DNodes() ([]*model.DNode, error) { + return s.dao.GetDNodes() +} diff --git a/service/discovery/service/service.go b/service/discovery/service/service.go new file mode 100644 index 0000000..78e2f91 --- /dev/null +++ b/service/discovery/service/service.go @@ -0,0 +1,32 @@ +package service + +import ( + "context" + "github.com/inconshreveable/log15" + "gitlab.33.cn/chat/dtalk/service/discovery/config" + "gitlab.33.cn/chat/dtalk/service/discovery/dao" +) + +type Service struct { + log log15.Logger + cfg *config.Config + dao *dao.Dao +} + +func New(cfg *config.Config) *Service { + s := &Service{ + log: log15.New("module", "discovery/svc"), + cfg: cfg, + dao: dao.New(cfg), + } + s.StoreNodes(cfg.CNodes, cfg.DNodes) + return s +} + +func Ping(c context.Context) error { + return nil +} + +func (s *Service) Config() *config.Config { + return s.cfg +} diff --git a/service/discovery/version.go b/service/discovery/version.go new file mode 100644 index 0000000..d54326e --- /dev/null +++ b/service/discovery/version.go @@ -0,0 +1,16 @@ +package version + +//var ( +// // The full version string +// Version = "0.0.1" +// +// // GitCommit is set with --ldflags "-X main.gitCommit=$(git rev-parse --short=8 HEAD)" +// GitCommit string +//) +// +//func GetVersion() string { +// if GitCommit != "" { +// return Version + "-" + GitCommit +// } +// return Version +//} diff --git a/service/generator/CHANGELOG.md b/service/generator/CHANGELOG.md new file mode 100644 index 0000000..a7017ac --- /dev/null +++ b/service/generator/CHANGELOG.md @@ -0,0 +1,21 @@ +版本号`major.minor.patch`具体规则如下: +- major:主版本号,如有重大版本重构则该字段递增,通常各主版本间接口不兼容。 +- minor:次版本号,各次版本号间接口保持兼容,如有接口新增或优化则该字段递增。 +- patch:补丁号,如有功能改善或缺陷修复则该字段递增。 + + +## version 0.0 + +init generator v0.0.1 +- 更新 etcdv3.5.0 v0.0.2 + + +## example x.x.x @yy.mm.dd + +**Feature** + +**Bug Fixes** + +**Improvement** + +**Breaking Change** diff --git a/service/generator/Makefile b/service/generator/Makefile new file mode 100644 index 0000000..fce202c --- /dev/null +++ b/service/generator/Makefile @@ -0,0 +1,43 @@ +# golang1.9 or latest +# 1. make help +# 2. make dep +# 3. make build +# ... + +VERSION := $(shell echo $(shell cat cmd/main.go | grep "projectVersion =" | cut -d '=' -f2)) +APP_NAME := generator +BUILD_DIR := build +APP := ${BUILD_DIR}/${APP_NAME}_v${VERSION} +PKG_NAME := ${APP_NAME}_v${VERSION} +PKG := ${PKG_NAME}.tar.gz + +main_path = "main" +go_version = $(shell go version | awk '{ print $3 }') +build_time = $(shell date "+%Y-%m-%d %H:%M:%S %Z") +git_commit = $(shell git rev-parse --short=10 HEAD) +flags := -ldflags "-X '${main_path}.goVersion=${go_version}' \ +-X '${main_path}.buildTime=${build_time}' \ +-X '${main_path}.gitCommit=${git_commit}' \ +-X 'google.golang.org/protobuf/reflect/protoregistry.conflictPolicy=warn'" + +.PHONY: clean build pkg + +clean: ## Remove previous build + @rm -rf ${BUILD_DIR} + @go clean + +build: #checkgofmt ## Build the binary file + GOOS=linux GOARCH=amd64 GO111MODULE=on GOPROXY=https://goproxy.cn,direct GOSUMDB="sum.golang.google.cn" go build -v $(flags) -o $(APP) cmd/main.go + +pkg: build ## Package + mkdir -p ${PKG_NAME}/bin + mkdir -p ${PKG_NAME}/etc + cp ${APP} ${PKG_NAME}/bin/ + cp etc/* ${PKG_NAME}/etc/ + tar zvcf ${PKG} ${PKG_NAME} + rm -rf ${PKG_NAME} + +REMOTE_BIN_PATH := /opt/dtalk/srv/app/bin +upload: build + rsync -r $(APP) 107:$(REMOTE_BIN_PATH) + ssh 107 "cd $(REMOTE_BIN_PATH) && chmod 777 $(PKG_NAME) && ln -sf $(PKG_NAME) $(APP_NAME) && supervisorctl restart $(APP_NAME)" \ No newline at end of file diff --git a/service/generator/api/api.pb.go b/service/generator/api/api.pb.go new file mode 100644 index 0000000..9d3ed67 --- /dev/null +++ b/service/generator/api/api.pb.go @@ -0,0 +1,204 @@ +// protoc -I=. -I=$GOPATH/src --go_out=plugins=grpc:. *.proto + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc v3.19.1 +// source: api.proto + +package generator + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Empty struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *Empty) Reset() { + *x = Empty{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Empty) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Empty) ProtoMessage() {} + +func (x *Empty) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Empty.ProtoReflect.Descriptor instead. +func (*Empty) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{0} +} + +type GetIDReply struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *GetIDReply) Reset() { + *x = GetIDReply{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetIDReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetIDReply) ProtoMessage() {} + +func (x *GetIDReply) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetIDReply.ProtoReflect.Descriptor instead. +func (*GetIDReply) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{1} +} + +func (x *GetIDReply) GetId() int64 { + if x != nil { + return x.Id + } + return 0 +} + +var File_api_proto protoreflect.FileDescriptor + +var file_api_proto_rawDesc = []byte{ + 0x0a, 0x09, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x64, 0x74, 0x61, + 0x6c, 0x6b, 0x2e, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x22, 0x07, 0x0a, 0x05, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x1c, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x49, 0x44, 0x52, 0x65, + 0x70, 0x6c, 0x79, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x02, 0x69, 0x64, 0x32, 0x49, 0x0a, 0x09, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, + 0x12, 0x3c, 0x0a, 0x05, 0x47, 0x65, 0x74, 0x49, 0x44, 0x12, 0x16, 0x2e, 0x64, 0x74, 0x61, 0x6c, + 0x6b, 0x2e, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x45, 0x6d, 0x70, 0x74, + 0x79, 0x1a, 0x1b, 0x2e, 0x64, 0x74, 0x61, 0x6c, 0x6b, 0x2e, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, + 0x74, 0x6f, 0x72, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x44, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x42, 0x2b, + 0x5a, 0x29, 0x67, 0x69, 0x74, 0x6c, 0x61, 0x62, 0x2e, 0x33, 0x33, 0x2e, 0x63, 0x6e, 0x2f, 0x63, + 0x68, 0x61, 0x74, 0x2f, 0x64, 0x74, 0x61, 0x6c, 0x6b, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, +} + +var ( + file_api_proto_rawDescOnce sync.Once + file_api_proto_rawDescData = file_api_proto_rawDesc +) + +func file_api_proto_rawDescGZIP() []byte { + file_api_proto_rawDescOnce.Do(func() { + file_api_proto_rawDescData = protoimpl.X.CompressGZIP(file_api_proto_rawDescData) + }) + return file_api_proto_rawDescData +} + +var file_api_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_api_proto_goTypes = []interface{}{ + (*Empty)(nil), // 0: dtalk.generator.Empty + (*GetIDReply)(nil), // 1: dtalk.generator.GetIDReply +} +var file_api_proto_depIdxs = []int32{ + 0, // 0: dtalk.generator.Generator.GetID:input_type -> dtalk.generator.Empty + 1, // 1: dtalk.generator.Generator.GetID:output_type -> dtalk.generator.GetIDReply + 1, // [1:2] is the sub-list for method output_type + 0, // [0:1] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_api_proto_init() } +func file_api_proto_init() { + if File_api_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_api_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Empty); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetIDReply); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_api_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_api_proto_goTypes, + DependencyIndexes: file_api_proto_depIdxs, + MessageInfos: file_api_proto_msgTypes, + }.Build() + File_api_proto = out.File + file_api_proto_rawDesc = nil + file_api_proto_goTypes = nil + file_api_proto_depIdxs = nil +} diff --git a/service/generator/api/api.proto b/service/generator/api/api.proto new file mode 100644 index 0000000..6fcdeb6 --- /dev/null +++ b/service/generator/api/api.proto @@ -0,0 +1,15 @@ +// protoc -I=. -I=$GOPATH/src --go_out=plugins=grpc:. *.proto +syntax = "proto3"; + +package dtalk.generator; +option go_package = "gitlab.33.cn/chat/dtalk/service/generator"; + +message Empty{} + +message GetIDReply { + int64 id = 1; +} + +service Generator { + rpc GetID(Empty) returns (GetIDReply); +} diff --git a/service/generator/api/api_grpc.pb.go b/service/generator/api/api_grpc.pb.go new file mode 100644 index 0000000..7498222 --- /dev/null +++ b/service/generator/api/api_grpc.pb.go @@ -0,0 +1,101 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. + +package generator + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// GeneratorClient is the client API for Generator service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type GeneratorClient interface { + GetID(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*GetIDReply, error) +} + +type generatorClient struct { + cc grpc.ClientConnInterface +} + +func NewGeneratorClient(cc grpc.ClientConnInterface) GeneratorClient { + return &generatorClient{cc} +} + +func (c *generatorClient) GetID(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*GetIDReply, error) { + out := new(GetIDReply) + err := c.cc.Invoke(ctx, "/dtalk.generator.Generator/GetID", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// GeneratorServer is the server API for Generator service. +// All implementations must embed UnimplementedGeneratorServer +// for forward compatibility +type GeneratorServer interface { + GetID(context.Context, *Empty) (*GetIDReply, error) + mustEmbedUnimplementedGeneratorServer() +} + +// UnimplementedGeneratorServer must be embedded to have forward compatible implementations. +type UnimplementedGeneratorServer struct { +} + +func (UnimplementedGeneratorServer) GetID(context.Context, *Empty) (*GetIDReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetID not implemented") +} +func (UnimplementedGeneratorServer) mustEmbedUnimplementedGeneratorServer() {} + +// UnsafeGeneratorServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to GeneratorServer will +// result in compilation errors. +type UnsafeGeneratorServer interface { + mustEmbedUnimplementedGeneratorServer() +} + +func RegisterGeneratorServer(s grpc.ServiceRegistrar, srv GeneratorServer) { + s.RegisterService(&Generator_ServiceDesc, srv) +} + +func _Generator_GetID_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GeneratorServer).GetID(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.generator.Generator/GetID", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GeneratorServer).GetID(ctx, req.(*Empty)) + } + return interceptor(ctx, in, info, handler) +} + +// Generator_ServiceDesc is the grpc.ServiceDesc for Generator service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Generator_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "dtalk.generator.Generator", + HandlerType: (*GeneratorServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetID", + Handler: _Generator_GetID_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "api.proto", +} diff --git a/service/generator/api/client.go b/service/generator/api/client.go new file mode 100644 index 0000000..58b0a99 --- /dev/null +++ b/service/generator/api/client.go @@ -0,0 +1,78 @@ +package generator + +import ( + "context" + "fmt" + "gitlab.33.cn/chat/dtalk/pkg/naming" + "gitlab.33.cn/chat/dtalk/pkg/net/grpc" + "google.golang.org/grpc/resolver" + "time" +) + +// AppID unique app id for service discovery +//const AppID = "identify.service.generator" + +type Client struct { + client GeneratorClient +} + +func New(etcdAddr, schema, srvName string, dial time.Duration) *Client { + rb := naming.NewResolver(etcdAddr, schema) + resolver.Register(rb) + + addr := fmt.Sprintf("%s:///%s", schema, srvName) // "schema://[authority]/service" + fmt.Println("generator rpc client call addr:", addr) + + conn, err := grpc.NewGRPCConn(addr, dial) + if err != nil { + panic(err) + } + return &Client{ + client: NewGeneratorClient(conn), + } +} + +func (c *Client) GetID() (int64, error) { + reply, err := c.client.GetID(context.Background(), &Empty{}) + if reply == nil { + return 0, err + } + return reply.Id, err +} + +/* + +type defaultGenerator struct { + client idgen.GeneratorClient +} + +// NewDefaultGenerator . +// etcdAddr like "127.0.0.1:2379;127.0.0.2:2379;" +func NewDefaultGenerator(etcdAddr, schema, srvName string, dial time.Duration) *defaultGenerator { + rb := naming.NewResolver(etcdAddr, schema) + resolver.Register(rb) + + addr := fmt.Sprintf("%s:///%s", schema, srvName) // "schema://[authority]/service" + fmt.Println("generator rpc client call addr:", addr) + + conn, err := common.NewGRPCConn(addr, dial) + if err != nil { + panic(err) + } + return &defaultGenerator{client: idgen.NewGeneratorClient(conn)} +} + +func (g *defaultGenerator) GetId() (int64, error) { + var ( + req idgen.Empty + reply *idgen.GetIDReply + ) + reply, err := g.client.GetID(context.Background(), &req) + if err != nil { + return 0, errors.WithMessagef(err, "getLogId") + } + + return reply.Id, nil +} + +*/ diff --git a/service/generator/api/create.sh b/service/generator/api/create.sh new file mode 100644 index 0000000..0e5dbf9 --- /dev/null +++ b/service/generator/api/create.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +protoc -I. -I$GOPATH/src \ + --go_out=. --go_opt=paths=source_relative \ + --go-grpc_out=. --go-grpc_opt=paths=source_relative \ + *.proto \ No newline at end of file diff --git a/service/generator/cmd/main.go b/service/generator/cmd/main.go new file mode 100644 index 0000000..f3133e5 --- /dev/null +++ b/service/generator/cmd/main.go @@ -0,0 +1,97 @@ +package main + +import ( + "context" + "flag" + "fmt" + "net" + "os" + "os/signal" + "syscall" + "time" + + "github.com/Terry-Mao/goim/pkg/ip" + "github.com/inconshreveable/log15" + "gitlab.33.cn/chat/dtalk/pkg/naming" + "gitlab.33.cn/chat/dtalk/service/generator/config" + "gitlab.33.cn/chat/dtalk/service/generator/server/grpc" + "gitlab.33.cn/chat/dtalk/service/generator/service" +) + +const srvName = "generator" + +var log = log15.New("cmd", srvName) + +var ( + // projectVersion 项目版本 + projectVersion = "0.0.2" + // goVersion go版本 + goVersion = "" + // gitCommit git提交commit id + gitCommit = "" + // buildTime 编译时间 + buildTime = "" + + isShowVersion = flag.Bool("v", false, "show project version") +) + +// showVersion 显示项目版本信息 +func showVersion(isShow bool) { + if isShow { + fmt.Printf("Project: %s\n", srvName) + fmt.Printf(" Version: %s\n", projectVersion) + fmt.Printf(" Go Version: %s\n", goVersion) + fmt.Printf(" Git Commit: %s\n", gitCommit) + fmt.Printf(" Build Time: %s\n", buildTime) + os.Exit(0) + } +} + +func main() { + flag.Parse() + showVersion(*isShowVersion) + + if err := config.Init(); err != nil { + panic(err) + } + log.Info("config info:", + "Node", config.Conf.Node) + // service init + svc := service.New(config.Conf) + rpc := grpc.New(config.Conf.GRPCServer, svc) + + // register server + _, port, _ := net.SplitHostPort(config.Conf.GRPCServer.Addr) + addr := fmt.Sprintf("%s:%s", ip.InternalIP(), port) + if err := naming.Register(config.Conf.Reg.RegAddrs, config.Conf.Reg.SrvName, addr, config.Conf.Reg.Schema, 15); err != nil { + panic(err) + } + fmt.Println("register ok") + + // init signal + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT) + for { + s := <-c + log.Info("service get a signal %s", s.String()) + switch s { + case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT: + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + time.Sleep(time.Second * 2) + if err := naming.UnRegister(config.Conf.Reg.SrvName, addr, config.Conf.Reg.Schema); err != nil { + log.Error("naming.UnRegister", "err", err) + } + if err := rpc.Shutdown(ctx); err != nil { + log.Error("rpc.Shutdown", "err", err) + } + + log.Info(srvName + " server exit") + return + case syscall.SIGHUP: + // TODO reload + default: + return + } + } +} diff --git a/service/generator/config/config.go b/service/generator/config/config.go new file mode 100644 index 0000000..42dc73f --- /dev/null +++ b/service/generator/config/config.go @@ -0,0 +1,67 @@ +package config + +import ( + "flag" + "os" + "time" + + "github.com/BurntSushi/toml" + "gitlab.33.cn/chat/dtalk/pkg/net/grpc" + xgrpc "gitlab.33.cn/chat/dtalk/pkg/net/grpc" + xtime "gitlab.33.cn/chat/dtalk/pkg/time" +) + +var ( + confPath string + regAddrs string + + // Conf config + Conf *Config +) + +func init() { + var ( + defAddrs = os.Getenv("REGADDRS") + ) + flag.StringVar(&confPath, "conf", "generator.toml", "default config path.") + flag.StringVar(®Addrs, "reg", defAddrs, "etcd register addrs. eg:127.0.0.1:2379") +} + +// Init init config. +func Init() (err error) { + Conf = Default() + _, err = toml.DecodeFile(confPath, &Conf) + return +} + +// Default new a config with specified defualt value. +func Default() *Config { + return &Config{ + Node: 1, + GRPCServer: &xgrpc.ServerConfig{ + Network: "tcp", + Addr: ":30002", + Timeout: xtime.Duration(time.Second), + KeepAliveMaxConnectionIdle: xtime.Duration(time.Second * 60), + KeepAliveMaxConnectionAge: xtime.Duration(time.Hour * 2), + KeepAliveMaxMaxConnectionAgeGrace: xtime.Duration(time.Second * 20), + KeepAliveTime: xtime.Duration(time.Second * 60), + KeepAliveTimeout: xtime.Duration(time.Second * 20), + }, + } +} + +type Config struct { + Node int64 + //reg + Reg *Reg + //gRPC server + GRPCServer *grpc.ServerConfig +} + +// Reg is service register/discovery config +type Reg struct { + Schema string + SrvName string + RegAddrs string // etcd addrs, seperate by ',' +} diff --git a/service/generator/config/generator.toml b/service/generator/config/generator.toml new file mode 100644 index 0000000..9db8b4b --- /dev/null +++ b/service/generator/config/generator.toml @@ -0,0 +1,16 @@ +Node=1 + +[reg] +Schema = "dtalk" +SrvName = "generator" +RegAddrs = "127.0.0.1:2379" + +[GRPCServer] +Network= "tcp" +Addr= ":30002" +Timeout= "1s" +KeepAliveMaxConnectionIdle= "60s" +KeepAliveMaxConnectionAge= "2h" +KeepAliveMaxMaxConnectionAgeGrace= "20s" +KeepAliveTime= "60s" +KeepAliveTimeout= "20s" \ No newline at end of file diff --git a/service/generator/server/grpc/server.go b/service/generator/server/grpc/server.go new file mode 100644 index 0000000..555ae3d --- /dev/null +++ b/service/generator/server/grpc/server.go @@ -0,0 +1,33 @@ +package grpc + +import ( + "context" + "time" + + xgrpc "gitlab.33.cn/chat/dtalk/pkg/net/grpc" + pb "gitlab.33.cn/chat/dtalk/service/generator/api" + "gitlab.33.cn/chat/dtalk/service/generator/service" + "google.golang.org/grpc" +) + +func New(c *xgrpc.ServerConfig, svr *service.Service) *xgrpc.Server { + connectionTimeout := grpc.ConnectionTimeout(time.Duration(c.Timeout)) + ws := xgrpc.NewServer(c, connectionTimeout) + pb.RegisterGeneratorServer(ws.Server(), &server{pb.UnimplementedGeneratorServer{}, svr}) + ws, err := ws.Start() + if err != nil { + panic(err) + } + return ws +} + +type server struct { + pb.UnimplementedGeneratorServer + svr *service.Service +} + +func (s *server) GetID(ctx context.Context, req *pb.Empty) (*pb.GetIDReply, error) { + return &pb.GetIDReply{ + Id: s.svr.GetID(), + }, nil +} diff --git a/service/generator/service/service.go b/service/generator/service/service.go new file mode 100644 index 0000000..a5b6411 --- /dev/null +++ b/service/generator/service/service.go @@ -0,0 +1,25 @@ +package service + +import ( + "gitlab.33.cn/chat/dtalk/pkg/util" + "gitlab.33.cn/chat/dtalk/service/generator/config" +) + +type Service struct { + idGenerator *util.Snowflake +} + +func New(c *config.Config) *Service { + g, err := util.NewSnowflake(c.Node) + if err != nil { + panic(err) + } + s := &Service{ + idGenerator: g, + } + return s +} + +func (s *Service) GetID() int64 { + return s.idGenerator.NextId() +} diff --git a/service/generator/version.go b/service/generator/version.go new file mode 100644 index 0000000..24d065e --- /dev/null +++ b/service/generator/version.go @@ -0,0 +1,16 @@ +package version + +//var ( +// // The full version string +// Version = "0.0.2" +// +// // GitCommit is set with --ldflags "-X main.gitCommit=$(git rev-parse --short=8 HEAD)" +// GitCommit string +//) +// +//func GetVersion() string { +// if GitCommit != "" { +// return Version + "-" + GitCommit +// } +// return Version +//} diff --git a/service/group/CHANGELOG.md b/service/group/CHANGELOG.md new file mode 100644 index 0000000..bb0cd45 --- /dev/null +++ b/service/group/CHANGELOG.md @@ -0,0 +1,150 @@ +版本号`major.minor.patch`具体规则如下: + +- major:主版本号,如有重大版本重构则该字段递增,通常各主版本间接口不兼容。 +- minor:次版本号,各次版本号间接口保持兼容,如有接口新增或优化则该字段递增。 +- patch:补丁号,如有功能改善或缺陷修复则该字段递增。 + +## version 2.3.0 2022_01_20 + +**Feature** + +- 增加一堆 rpc 方法 +- 关闭 http 服务 + +## version 2.2.0 2022_01_06 + +**Feature** + +- [x] 批量加群 rpc 接口 v2.2.1 +- [x] 批量退群 rpc 接口 v2.2.2 +- [x] 交换群主 rpc 接口 v2.2.2 +- 修改创群 RPC 接口 v2.2.3 + +## version 2.1.0 2021_12_23 + +**数据库变动** + +- dtalk_group_info 增加 group_type 字段 (0: 普通群, 1: 全员群, 2: 部门群) + +## version 2.0.6 + +**配置文件新增** + +```toml +[Redis] +network = "tcp" +addr = "127.0.0.1:6379" +auth = "" +active = 60000 +idle = 1024 +dialTimeout = "200ms" +readTimeout = "500ms" +writeTimeout = "500ms" +idleTimeout = "120s" +expire = "30m" +``` + +**Feature** +- 实现退群 RPC 方法 2021_11_26_17_58_11 +- 使用日志中间件记录请求, 日志设置 TraceId +- rpc log 增加 trace id 2021_12_06 +- 所有方法增加 ctx +- 增加解散群 RPC 方法 +- group 增加 cache 6 2021_12_07_15_37_50 + +## version 2.0.5 2021_11_05- + +**数据库更新** +- group_name 修改长度 +- dtalk_group_info 新增 group_aes_key 字段 +- dtalk_group_info 新增 group_pub_name 字段 + +**Feature** +- 增加 aes 字段 v2.0.1 2021_11_05 +- 增加 group_pub_name 字段 v2.0.2 2021_11_17 +- 增加查询群信息 rpc 接口 v2.0.3 2021_11_18 +- 增加 maintain 数据库名称 v2.0.4 2021_11_19 +- 修复更新群名称重复发送通知的特性 v2.0.5 2021_11_23 + +## version 2.0 2021_11_03 + +**配置文件更新** + +group.toml +所有 `[xxxRPCClient]` 增加 `RegAddrs = "127.0.0.1:2379"` 字段 + +- 重构 group 代码 v2.0.0 2021_11_03 + + +## version 1.6 + +**Feature** +- 增加加群申请接口 v1.6.0 2021.10.9 +- 增加查询群成员 rpc 接口 v1.6.1 2021.10.10 +- 改用 imparse 中的 proto @v1.6.2 2021.10.14 + +## version 1.5 + +**Feature** +- 增加加群 rpc 接口 v1.5.0 2021.9.24 +- 增加搜索群列表接口 v1.5.4 2021.9.26 + +**Bug fix** +- 修复签名过期 bug v1.5.1 2021.9.26 +- 生成不重复的 groupMarkId v1.5.2 2021.9.26 +- 优化 groupMarkId 生成方法 v1.5.3 2021.9.26 +- 修复批量邀请群成员全失败的逻辑 v1.5.5 2021.9.27 +- 修复创建群聊时群成员包含群主的产生的 bug v1.5.6 2021.9.27 +- 修复成员主动退群后还能收到退群的通知 v1.5.7 2021.9.28 + +## version 1.4 + +**Feature** +- 增加查询群列表 rpc 接口 @v1.4.0 2021.9.2 +- 增加直接加群的接口 @v1.4.1 2021.9.6 +- 查询群公开信息接口返回用户在群里的信息 v1.4.2 2021.9.9 +- 扫码加群走邀请加群通道 v1.4.3 2021.9.9 + +## version 1.3 + +**Feature** +- 增加创群 rpc 接口 @v1.3.0 2021.8.23 +- 接口可以选择只传整形或者字符串类型 ID @v1.3.1 2021.8.26 + +## version 1.2 + +**Feature** +- 接口增加 string 类型 Id @v1.2.0 2021.8.20 + +## version 1.1 @2021.7.26 + +**Feature** + +- 群人数上限和管理员数量上限可以通过配置文件配置 @v1.1.1 2021.7.7 +- notice 从 answer 服务接入 @v1.1.2 2021.7.15 +- change alert.from from groupId to personId @v1.1.3 2021.7.26 + +**Bug fix** +- 群主不能把自己设置为管理员 @v1.1.4 2021.8.17 + +## version 1.1.0 @2021.7.1 + +**Feature** + +增加 Prometheus 的指标监控 +- http_requests_total +- http_response_time_seconds + +## version 1.0.0 @2021.6.30 + +group init, 实现第一批需求接口 + +## example x.x.x @yy.mm.dd + +**Feature** + +**Bug Fixes** + +**Improvement** + +**Breaking Change** diff --git a/service/group/Makefile b/service/group/Makefile new file mode 100644 index 0000000..0aba399 --- /dev/null +++ b/service/group/Makefile @@ -0,0 +1,51 @@ +VERSION := $(shell echo $(shell cat cmd/main.go | grep "projectVersion =" | cut -d '=' -f2)) +APP_NAME := group +BUILD_DIR := build +APP := ${BUILD_DIR}/${APP_NAME}_v${VERSION} +PKG_NAME := ${APP_NAME}_v${VERSION} +PKG := ${PKG_NAME}.tar.gz + +.PHONY: clean build pkg + +main_path = "main" +go_version = $(shell go version | awk '{ print $3 }') +build_time = $(shell date "+%Y-%m-%d %H:%M:%S %Z") +git_commit = $(shell git rev-parse --short=10 HEAD) +flags := -ldflags "-X '${main_path}.goVersion=${go_version}' \ +-X '${main_path}.buildTime=${build_time}' \ +-X '${main_path}.gitCommit=${git_commit}' \ +-X 'google.golang.org/protobuf/reflect/protoregistry.conflictPolicy=warn'" + +clean: ## Remove previous build + @rm -rf ${BUILD_DIR} + @go clean + +swag: + @echo '┌ start gen swag' + @swag init -g server/http/http.go + @echo '└ end gen swag' + +build: swag #checkgofmt ## Build the binary file + @echo '┌ start build $(APP)' + @GOOS=linux GOARCH=amd64 GO111MODULE=on GOPROXY=https://goproxy.cn,direct GOSUMDB="sum.golang.google.cn" go build -v ${flags} -o $(APP) cmd/main.go + @echo '└ end build $(APP)' + +pkg: build ## Package + mkdir -p ${PKG_NAME}/bin + mkdir -p ${PKG_NAME}/etc + cp ${APP} ${PKG_NAME}/bin/ + cp config/$(APP_NAME).toml ${PKG_NAME}/etc/ + tar zvcf ${PKG} ${PKG_NAME} + rm -rf ${PKG_NAME} + +run: swag + @echo '┌ start run $(APP)' + @go run cmd/main.go -conf config/group.toml + @echo '└ end run $(APP)' + +REMOTE_BIN_PATH := /opt/dtalk/srv/app/bin +upload: build + @echo '┌ start upload $(APP)' + @rsync -r $(APP) 107:$(REMOTE_BIN_PATH) + @ssh 107 "cd $(REMOTE_BIN_PATH) && chmod 777 $(PKG_NAME) && ln -sf $(PKG_NAME) $(APP_NAME) && supervisorctl restart $(APP_NAME)" + @echo '┌ start upload $(APP)' \ No newline at end of file diff --git a/service/group/README.md b/service/group/README.md new file mode 100644 index 0000000..c90048f --- /dev/null +++ b/service/group/README.md @@ -0,0 +1,79 @@ +# group 群组服务 + +## TODO + +- [ ] 删除service 中所有带 Svc 后缀的方法 +- [ ] dao 层暴露的方法把 biz 对象作为方法参数, 而不是 db + +## 目录结构 + +``` +group +├── api +├── build +├── cmd +├── config +├── dao +├── docs +├── logic +│ └── http +├── model +│ ├── biz +│ ├── db +│ └── types +├── server +│ ├── grpc +│ └── http +└── service +``` + +### api + +包含 grpc 的 `proto` , `*.pb.go`, `client` 文件 +client 提供 rpc 客户端的创建方法, 相当于手动给 grpc 客户端包了一层 + +### build + +make build 指令生成的二进制文件保存目录 + +### cmd + +项目启动目录 +包括资源的初始化, http 和 rpc 服务的启动 + +### config + +配置文件描述目录 + +### dao + +数据访问层 +注入到 service 中, 暴露数据操作方法, 不区分是操作数据库还是操作缓存(比如获得群信息, service 不关心是从数据库中拿还是从缓存中拿). +如有需要,可以与业务逻辑服务分开,单独暴露 rpc 方法, 提供给多个业务服务,类似: +![](https://pic3.zhimg.com/80/v2-bf7456dbea800d3b7bc209ede853b7a6_720w.jpg) + +### docs + +http api swag 描述文件, 由 `make swag` 生成 +可以删去, 以后统一由 gateway 服务管理 + +### logic + +使用`service`中的各种方法编排业务逻辑, 包含参数校验, 权限判断 + +### model + +包含各种用到的结构体与 const 值 + +- `biz` service 中一直使用的结构体 +- `db` 数据库字段映射(可以移动到 dao 层) +- `types` http服务的请求与返回(可以不要,因为 http 请求都移动到 gateway 了) + +### server + +http 服务和 grpc 服务的启动路径, 仅绑定参数, 不做任何处理 +服务中的各个方法与 logic 中的方法一一对应 + +### service + +各种业务方法, 假定传入的参数都是正确,经过验证的, 不进行权限判断 diff --git a/service/group/api/client.go b/service/group/api/client.go new file mode 100644 index 0000000..1bb1bcb --- /dev/null +++ b/service/group/api/client.go @@ -0,0 +1,220 @@ +package group + +import ( + "context" + "fmt" + "time" + + "gitlab.33.cn/chat/dtalk/pkg/naming" + xgrpc "gitlab.33.cn/chat/dtalk/pkg/net/grpc" + "google.golang.org/grpc" + "google.golang.org/grpc/resolver" +) + +type Client struct { + conn *grpc.ClientConn + client GroupClient +} + +func New(etcdAddr, schema, srvName string, dial time.Duration, opts ...grpc.DialOption) *Client { + rb := naming.NewResolver(etcdAddr, schema) + resolver.Register(rb) + + addr := fmt.Sprintf("%s:///%s", schema, srvName) // "schema://[authority]/service" + fmt.Println("group rpc client call addr:", addr) + + conn, err := xgrpc.NewGRPCConnWithOpts(addr, dial, opts...) + if err != nil { + panic(err) + } + return &Client{ + conn: conn, + client: NewGroupClient(conn), + } +} + +func (c *Client) GetGroupIds(ctx context.Context, memberId string) ([]int64, error) { + in := &GetGroupIdsRequest{ + MemberId: memberId, + } + res, err := c.client.GetGroupIds(ctx, in) + if err != nil { + return nil, err + } + return res.GroupIds, nil +} + +func (c *Client) CheckInGroup(ctx context.Context, memberId string, groupId int64) (bool, error) { + in := &CheckInGroupRequest{ + MemberId: memberId, + GroupId: groupId, + } + res, err := c.client.CheckInGroup(ctx, in) + if err != nil { + return false, err + } + return res.IsOk, err +} + +func (c *Client) GetMemberIds(ctx context.Context, groupId int64) ([]string, error) { + in := &GetMemberIdsRequest{ + GroupId: groupId, + } + res, err := c.client.GetMemberIds(ctx, in) + if err != nil { + return nil, err + } + return res.MemberIds, err +} + +func (c *Client) CheckMute(ctx context.Context, memberId string, groupId int64) (bool, error) { + in := &CheckMuteRequest{ + MemberId: memberId, + GroupId: groupId, + } + res, err := c.client.CheckMute(ctx, in) + if err != nil { + return false, err + } + return res.IsOk, nil +} + +type Group struct { + Id int64 + Name string + Avatar string +} + +func (c *Client) GetGroups(ctx context.Context, id string) ([]*Group, error) { + in := &GetGroupsReq{ + Id: id, + } + res, err := c.client.GetGroups(ctx, in) + if err != nil { + return nil, err + } + groups := make([]*Group, len(res.Groups)) + for i, group := range res.Groups { + groups[i] = &Group{ + Id: group.Id, + Name: group.Name, + Avatar: group.Avatar, + } + } + + return groups, nil +} + +func (c *Client) GetMember(ctx context.Context, groupId int64, memberId string) (*GetMemberResp, error) { + in := &GetMemberReq{ + MemberId: memberId, + GroupId: groupId, + } + res, err := c.client.GetMember(ctx, in) + if err != nil { + return nil, err + } + return res, nil +} + +// ------------------ new proto client ---------------------------------- + +func (c *Client) ChangeOwner(ctx context.Context, req *ChangeOwnerReq) (*ChangeOwnerResp, error) { + client := NewGroupClient(c.conn) + return client.ChangeOwner(ctx, req) +} + +func (c *Client) CreateGroup(ctx context.Context, req *CreateGroupReq) (*CreateGroupResp, error) { + client := NewGroupClient(c.conn) + return client.CreateGroup(ctx, req) +} + +func (c *Client) GetGroupList(ctx context.Context, req *GetGroupListReq) (*GetGroupListResp, error) { + client := NewGroupClient(c.conn) + return client.GetGroupList(ctx, req) +} + +func (c *Client) GetGroupMemberInfo(ctx context.Context, req *GetGroupMemberInfoReq) (*GetGroupMemberInfoResp, error) { + client := NewGroupClient(c.conn) + return client.GetGroupMemberInfo(ctx, req) +} + +func (c *Client) GetGroupMemberList(ctx context.Context, req *GetGroupMemberListReq) (*GetGroupMemberListResp, error) { + client := NewGroupClient(c.conn) + return client.GetGroupMemberList(ctx, req) +} + +func (c *Client) GetMuteList(ctx context.Context, req *GetMuteListReq) (*GetMuteListResp, error) { + client := NewGroupClient(c.conn) + return client.GetMuteList(ctx, req) +} + +func (c *Client) GetPriGroupInfo(ctx context.Context, req *GetPriGroupInfoReq) (*GetPriGroupInfoResp, error) { + client := NewGroupClient(c.conn) + return client.GetPriGroupInfo(ctx, req) +} + +func (c *Client) GetPubGroupInfo(ctx context.Context, req *GetPubGroupInfoReq) (*GetPubGroupInfoResp, error) { + client := NewGroupClient(c.conn) + return client.GetPubGroupInfo(ctx, req) +} + +func (c *Client) GroupDisband(ctx context.Context, req *GroupDisbandReq) (*GroupDisbandResp, error) { + client := NewGroupClient(c.conn) + return client.GroupDisband(ctx, req) +} + +func (c *Client) GroupExit(ctx context.Context, req *GroupExitReq) (*GroupExitResp, error) { + client := NewGroupClient(c.conn) + return client.GroupExit(ctx, req) +} + +func (c *Client) GroupRemove(ctx context.Context, req *GroupRemoveReq) (*GroupRemoveResp, error) { + client := NewGroupClient(c.conn) + return client.GroupRemove(ctx, req) +} + +func (c *Client) InviteGroupMembers(ctx context.Context, req *InviteGroupMembersReq) (*InviteGroupMembersResp, error) { + client := NewGroupClient(c.conn) + return client.InviteGroupMembers(ctx, req) +} + +func (c *Client) SetAdmin(ctx context.Context, req *SetAdminReq) (*SetAdminResp, error) { + client := NewGroupClient(c.conn) + return client.SetAdmin(ctx, req) +} + +func (c *Client) UpdateGroupAvatar(ctx context.Context, req *UpdateGroupAvatarReq) (*UpdateGroupAvatarResp, error) { + client := NewGroupClient(c.conn) + return client.UpdateGroupAvatar(ctx, req) +} + +func (c *Client) UpdateGroupFriendType(ctx context.Context, req *UpdateGroupFriendTypeReq) (*UpdateGroupFriendTypeResp, error) { + client := NewGroupClient(c.conn) + return client.UpdateGroupFriendType(ctx, req) +} + +func (c *Client) UpdateGroupJoinType(ctx context.Context, req *UpdateGroupJoinTypeReq) (*UpdateGroupJoinTypeResp, error) { + client := NewGroupClient(c.conn) + return client.UpdateGroupJoinType(ctx, req) +} + +func (c *Client) UpdateGroupMemberMuteTime(ctx context.Context, req *UpdateGroupMemberMuteTimeReq) (*UpdateGroupMemberMuteTimeResp, error) { + client := NewGroupClient(c.conn) + return client.UpdateGroupMemberMuteTime(ctx, req) +} + +func (c *Client) UpdateGroupMemberName(ctx context.Context, req *UpdateGroupMemberNameReq) (*UpdateGroupMemberNameResp, error) { + client := NewGroupClient(c.conn) + return client.UpdateGroupMemberName(ctx, req) +} + +func (c *Client) UpdateGroupMuteType(ctx context.Context, req *UpdateGroupMuteTypeReq) (*UpdateGroupMuteTypeResp, error) { + client := NewGroupClient(c.conn) + return client.UpdateGroupMuteType(ctx, req) +} + +func (c *Client) UpdateGroupName(ctx context.Context, req *UpdateGroupNameReq) (*UpdateGroupNameResp, error) { + client := NewGroupClient(c.conn) + return client.UpdateGroupName(ctx, req) +} diff --git a/service/group/api/create.sh b/service/group/api/create.sh new file mode 100644 index 0000000..0e5dbf9 --- /dev/null +++ b/service/group/api/create.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +protoc -I. -I$GOPATH/src \ + --go_out=. --go_opt=paths=source_relative \ + --go-grpc_out=. --go-grpc_opt=paths=source_relative \ + *.proto \ No newline at end of file diff --git a/service/group/api/group.pb.go b/service/group/api/group.pb.go new file mode 100644 index 0000000..13b294e --- /dev/null +++ b/service/group/api/group.pb.go @@ -0,0 +1,3980 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: group.proto + +package group + +import ( + fmt "fmt" + proto "github.com/golang/protobuf/proto" + math "math" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package + +// 群状态 +type GroupStatus int32 + +const ( + GroupStatus_GROUP_STATUS_NORMAL GroupStatus = 0 + GroupStatus_GROUP_STATUS_BLOCK GroupStatus = 1 + GroupStatus_GROUP_STATUS_DISBAND GroupStatus = 2 +) + +var GroupStatus_name = map[int32]string{ + 0: "GROUP_STATUS_NORMAL", + 1: "GROUP_STATUS_BLOCK", + 2: "GROUP_STATUS_DISBAND", +} + +var GroupStatus_value = map[string]int32{ + "GROUP_STATUS_NORMAL": 0, + "GROUP_STATUS_BLOCK": 1, + "GROUP_STATUS_DISBAND": 2, +} + +func (x GroupStatus) String() string { + return proto.EnumName(GroupStatus_name, int32(x)) +} + +func (GroupStatus) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{0} +} + +// 群类型 +type GroupType int32 + +const ( + GroupType_GROUP_TYPE_NORMAL GroupType = 0 + GroupType_GROUP_TYPE_ENT GroupType = 1 + GroupType_GROUP_TYPE_DEP GroupType = 2 +) + +var GroupType_name = map[int32]string{ + 0: "GROUP_TYPE_NORMAL", + 1: "GROUP_TYPE_ENT", + 2: "GROUP_TYPE_DEP", +} + +var GroupType_value = map[string]int32{ + "GROUP_TYPE_NORMAL": 0, + "GROUP_TYPE_ENT": 1, + "GROUP_TYPE_DEP": 2, +} + +func (x GroupType) String() string { + return proto.EnumName(GroupType_name, int32(x)) +} + +func (GroupType) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{1} +} + +// 群内加群设置 +type GroupJoinType int32 + +const ( + GroupJoinType_GROUP_JOIN_TYPE_ANY GroupJoinType = 0 + GroupJoinType_GROUP_JOIN_TYPE_ADMIN GroupJoinType = 1 + GroupJoinType_GROUP_JOIN_TYPE_APPLY GroupJoinType = 2 +) + +var GroupJoinType_name = map[int32]string{ + 0: "GROUP_JOIN_TYPE_ANY", + 1: "GROUP_JOIN_TYPE_ADMIN", + 2: "GROUP_JOIN_TYPE_APPLY", +} + +var GroupJoinType_value = map[string]int32{ + "GROUP_JOIN_TYPE_ANY": 0, + "GROUP_JOIN_TYPE_ADMIN": 1, + "GROUP_JOIN_TYPE_APPLY": 2, +} + +func (x GroupJoinType) String() string { + return proto.EnumName(GroupJoinType_name, int32(x)) +} + +func (GroupJoinType) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{2} +} + +// 群内发言设置 +type GroupMuteType int32 + +const ( + GroupMuteType_GROUP_MUTE_TYPE_ANY GroupMuteType = 0 + GroupMuteType_GROUP_MUTE_TYPE_ADMIN GroupMuteType = 1 +) + +var GroupMuteType_name = map[int32]string{ + 0: "GROUP_MUTE_TYPE_ANY", + 1: "GROUP_MUTE_TYPE_ADMIN", +} + +var GroupMuteType_value = map[string]int32{ + "GROUP_MUTE_TYPE_ANY": 0, + "GROUP_MUTE_TYPE_ADMIN": 1, +} + +func (x GroupMuteType) String() string { + return proto.EnumName(GroupMuteType_name, int32(x)) +} + +func (GroupMuteType) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{3} +} + +// 群内加好友设置 +type GroupFriendType int32 + +const ( + GroupFriendType_GROUP_FRIEND_TYPE_ALLOW GroupFriendType = 0 + GroupFriendType_GROUP_FRIEND_TYPE_DENY GroupFriendType = 1 +) + +var GroupFriendType_name = map[int32]string{ + 0: "GROUP_FRIEND_TYPE_ALLOW", + 1: "GROUP_FRIEND_TYPE_DENY", +} + +var GroupFriendType_value = map[string]int32{ + "GROUP_FRIEND_TYPE_ALLOW": 0, + "GROUP_FRIEND_TYPE_DENY": 1, +} + +func (x GroupFriendType) String() string { + return proto.EnumName(GroupFriendType_name, int32(x)) +} + +func (GroupFriendType) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{4} +} + +// 群成员类型 +type GroupMemberType int32 + +const ( + GroupMemberType_GROUP_MEMBER_TYPE_NORMAL GroupMemberType = 0 + GroupMemberType_GROUP_MEMBER_TYPE_ADMIN GroupMemberType = 1 + GroupMemberType_GROUP_MEMBER_TYPE_OWNER GroupMemberType = 2 + GroupMemberType_GROUP_MEMBER_TYPE_OTHER GroupMemberType = 10 +) + +var GroupMemberType_name = map[int32]string{ + 0: "GROUP_MEMBER_TYPE_NORMAL", + 1: "GROUP_MEMBER_TYPE_ADMIN", + 2: "GROUP_MEMBER_TYPE_OWNER", + 10: "GROUP_MEMBER_TYPE_OTHER", +} + +var GroupMemberType_value = map[string]int32{ + "GROUP_MEMBER_TYPE_NORMAL": 0, + "GROUP_MEMBER_TYPE_ADMIN": 1, + "GROUP_MEMBER_TYPE_OWNER": 2, + "GROUP_MEMBER_TYPE_OTHER": 10, +} + +func (x GroupMemberType) String() string { + return proto.EnumName(GroupMemberType_name, int32(x)) +} + +func (GroupMemberType) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{5} +} + +type GroupInfo struct { + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Avatar string `protobuf:"bytes,3,opt,name=avatar,proto3" json:"avatar,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GroupInfo) Reset() { *m = GroupInfo{} } +func (m *GroupInfo) String() string { return proto.CompactTextString(m) } +func (*GroupInfo) ProtoMessage() {} +func (*GroupInfo) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{0} +} + +func (m *GroupInfo) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GroupInfo.Unmarshal(m, b) +} +func (m *GroupInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GroupInfo.Marshal(b, m, deterministic) +} +func (m *GroupInfo) XXX_Merge(src proto.Message) { + xxx_messageInfo_GroupInfo.Merge(m, src) +} +func (m *GroupInfo) XXX_Size() int { + return xxx_messageInfo_GroupInfo.Size(m) +} +func (m *GroupInfo) XXX_DiscardUnknown() { + xxx_messageInfo_GroupInfo.DiscardUnknown(m) +} + +var xxx_messageInfo_GroupInfo proto.InternalMessageInfo + +func (m *GroupInfo) GetId() int64 { + if m != nil { + return m.Id + } + return 0 +} + +func (m *GroupInfo) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *GroupInfo) GetAvatar() string { + if m != nil { + return m.Avatar + } + return "" +} + +type GroupMemberInfo struct { + // 成员 id + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // 成员在群里的名字 + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GroupMemberInfo) Reset() { *m = GroupMemberInfo{} } +func (m *GroupMemberInfo) String() string { return proto.CompactTextString(m) } +func (*GroupMemberInfo) ProtoMessage() {} +func (*GroupMemberInfo) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{1} +} + +func (m *GroupMemberInfo) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GroupMemberInfo.Unmarshal(m, b) +} +func (m *GroupMemberInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GroupMemberInfo.Marshal(b, m, deterministic) +} +func (m *GroupMemberInfo) XXX_Merge(src proto.Message) { + xxx_messageInfo_GroupMemberInfo.Merge(m, src) +} +func (m *GroupMemberInfo) XXX_Size() int { + return xxx_messageInfo_GroupMemberInfo.Size(m) +} +func (m *GroupMemberInfo) XXX_DiscardUnknown() { + xxx_messageInfo_GroupMemberInfo.DiscardUnknown(m) +} + +var xxx_messageInfo_GroupMemberInfo proto.InternalMessageInfo + +func (m *GroupMemberInfo) GetId() string { + if m != nil { + return m.Id + } + return "" +} + +func (m *GroupMemberInfo) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +type GetGroupIdsRequest struct { + MemberId string `protobuf:"bytes,1,opt,name=member_id,json=memberId,proto3" json:"member_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetGroupIdsRequest) Reset() { *m = GetGroupIdsRequest{} } +func (m *GetGroupIdsRequest) String() string { return proto.CompactTextString(m) } +func (*GetGroupIdsRequest) ProtoMessage() {} +func (*GetGroupIdsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{2} +} + +func (m *GetGroupIdsRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetGroupIdsRequest.Unmarshal(m, b) +} +func (m *GetGroupIdsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetGroupIdsRequest.Marshal(b, m, deterministic) +} +func (m *GetGroupIdsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetGroupIdsRequest.Merge(m, src) +} +func (m *GetGroupIdsRequest) XXX_Size() int { + return xxx_messageInfo_GetGroupIdsRequest.Size(m) +} +func (m *GetGroupIdsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetGroupIdsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetGroupIdsRequest proto.InternalMessageInfo + +func (m *GetGroupIdsRequest) GetMemberId() string { + if m != nil { + return m.MemberId + } + return "" +} + +type GetGroupIdsReply struct { + GroupIds []int64 `protobuf:"varint,1,rep,packed,name=group_ids,json=groupIds,proto3" json:"group_ids,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetGroupIdsReply) Reset() { *m = GetGroupIdsReply{} } +func (m *GetGroupIdsReply) String() string { return proto.CompactTextString(m) } +func (*GetGroupIdsReply) ProtoMessage() {} +func (*GetGroupIdsReply) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{3} +} + +func (m *GetGroupIdsReply) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetGroupIdsReply.Unmarshal(m, b) +} +func (m *GetGroupIdsReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetGroupIdsReply.Marshal(b, m, deterministic) +} +func (m *GetGroupIdsReply) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetGroupIdsReply.Merge(m, src) +} +func (m *GetGroupIdsReply) XXX_Size() int { + return xxx_messageInfo_GetGroupIdsReply.Size(m) +} +func (m *GetGroupIdsReply) XXX_DiscardUnknown() { + xxx_messageInfo_GetGroupIdsReply.DiscardUnknown(m) +} + +var xxx_messageInfo_GetGroupIdsReply proto.InternalMessageInfo + +func (m *GetGroupIdsReply) GetGroupIds() []int64 { + if m != nil { + return m.GroupIds + } + return nil +} + +type CheckInGroupRequest struct { + MemberId string `protobuf:"bytes,1,opt,name=member_id,json=memberId,proto3" json:"member_id,omitempty"` + GroupId int64 `protobuf:"varint,2,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *CheckInGroupRequest) Reset() { *m = CheckInGroupRequest{} } +func (m *CheckInGroupRequest) String() string { return proto.CompactTextString(m) } +func (*CheckInGroupRequest) ProtoMessage() {} +func (*CheckInGroupRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{4} +} + +func (m *CheckInGroupRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_CheckInGroupRequest.Unmarshal(m, b) +} +func (m *CheckInGroupRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_CheckInGroupRequest.Marshal(b, m, deterministic) +} +func (m *CheckInGroupRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_CheckInGroupRequest.Merge(m, src) +} +func (m *CheckInGroupRequest) XXX_Size() int { + return xxx_messageInfo_CheckInGroupRequest.Size(m) +} +func (m *CheckInGroupRequest) XXX_DiscardUnknown() { + xxx_messageInfo_CheckInGroupRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_CheckInGroupRequest proto.InternalMessageInfo + +func (m *CheckInGroupRequest) GetMemberId() string { + if m != nil { + return m.MemberId + } + return "" +} + +func (m *CheckInGroupRequest) GetGroupId() int64 { + if m != nil { + return m.GroupId + } + return 0 +} + +type CheckInGroupReply struct { + IsOk bool `protobuf:"varint,1,opt,name=is_ok,json=isOk,proto3" json:"is_ok,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *CheckInGroupReply) Reset() { *m = CheckInGroupReply{} } +func (m *CheckInGroupReply) String() string { return proto.CompactTextString(m) } +func (*CheckInGroupReply) ProtoMessage() {} +func (*CheckInGroupReply) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{5} +} + +func (m *CheckInGroupReply) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_CheckInGroupReply.Unmarshal(m, b) +} +func (m *CheckInGroupReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_CheckInGroupReply.Marshal(b, m, deterministic) +} +func (m *CheckInGroupReply) XXX_Merge(src proto.Message) { + xxx_messageInfo_CheckInGroupReply.Merge(m, src) +} +func (m *CheckInGroupReply) XXX_Size() int { + return xxx_messageInfo_CheckInGroupReply.Size(m) +} +func (m *CheckInGroupReply) XXX_DiscardUnknown() { + xxx_messageInfo_CheckInGroupReply.DiscardUnknown(m) +} + +var xxx_messageInfo_CheckInGroupReply proto.InternalMessageInfo + +func (m *CheckInGroupReply) GetIsOk() bool { + if m != nil { + return m.IsOk + } + return false +} + +type GetMemberIdsRequest struct { + GroupId int64 `protobuf:"varint,1,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetMemberIdsRequest) Reset() { *m = GetMemberIdsRequest{} } +func (m *GetMemberIdsRequest) String() string { return proto.CompactTextString(m) } +func (*GetMemberIdsRequest) ProtoMessage() {} +func (*GetMemberIdsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{6} +} + +func (m *GetMemberIdsRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetMemberIdsRequest.Unmarshal(m, b) +} +func (m *GetMemberIdsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetMemberIdsRequest.Marshal(b, m, deterministic) +} +func (m *GetMemberIdsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetMemberIdsRequest.Merge(m, src) +} +func (m *GetMemberIdsRequest) XXX_Size() int { + return xxx_messageInfo_GetMemberIdsRequest.Size(m) +} +func (m *GetMemberIdsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetMemberIdsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetMemberIdsRequest proto.InternalMessageInfo + +func (m *GetMemberIdsRequest) GetGroupId() int64 { + if m != nil { + return m.GroupId + } + return 0 +} + +type GetMemberIdsReply struct { + MemberIds []string `protobuf:"bytes,1,rep,name=member_ids,json=memberIds,proto3" json:"member_ids,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetMemberIdsReply) Reset() { *m = GetMemberIdsReply{} } +func (m *GetMemberIdsReply) String() string { return proto.CompactTextString(m) } +func (*GetMemberIdsReply) ProtoMessage() {} +func (*GetMemberIdsReply) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{7} +} + +func (m *GetMemberIdsReply) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetMemberIdsReply.Unmarshal(m, b) +} +func (m *GetMemberIdsReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetMemberIdsReply.Marshal(b, m, deterministic) +} +func (m *GetMemberIdsReply) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetMemberIdsReply.Merge(m, src) +} +func (m *GetMemberIdsReply) XXX_Size() int { + return xxx_messageInfo_GetMemberIdsReply.Size(m) +} +func (m *GetMemberIdsReply) XXX_DiscardUnknown() { + xxx_messageInfo_GetMemberIdsReply.DiscardUnknown(m) +} + +var xxx_messageInfo_GetMemberIdsReply proto.InternalMessageInfo + +func (m *GetMemberIdsReply) GetMemberIds() []string { + if m != nil { + return m.MemberIds + } + return nil +} + +type CheckMuteRequest struct { + MemberId string `protobuf:"bytes,1,opt,name=member_id,json=memberId,proto3" json:"member_id,omitempty"` + GroupId int64 `protobuf:"varint,2,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *CheckMuteRequest) Reset() { *m = CheckMuteRequest{} } +func (m *CheckMuteRequest) String() string { return proto.CompactTextString(m) } +func (*CheckMuteRequest) ProtoMessage() {} +func (*CheckMuteRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{8} +} + +func (m *CheckMuteRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_CheckMuteRequest.Unmarshal(m, b) +} +func (m *CheckMuteRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_CheckMuteRequest.Marshal(b, m, deterministic) +} +func (m *CheckMuteRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_CheckMuteRequest.Merge(m, src) +} +func (m *CheckMuteRequest) XXX_Size() int { + return xxx_messageInfo_CheckMuteRequest.Size(m) +} +func (m *CheckMuteRequest) XXX_DiscardUnknown() { + xxx_messageInfo_CheckMuteRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_CheckMuteRequest proto.InternalMessageInfo + +func (m *CheckMuteRequest) GetMemberId() string { + if m != nil { + return m.MemberId + } + return "" +} + +func (m *CheckMuteRequest) GetGroupId() int64 { + if m != nil { + return m.GroupId + } + return 0 +} + +type CheckMuteReply struct { + IsOk bool `protobuf:"varint,1,opt,name=is_ok,json=isOk,proto3" json:"is_ok,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *CheckMuteReply) Reset() { *m = CheckMuteReply{} } +func (m *CheckMuteReply) String() string { return proto.CompactTextString(m) } +func (*CheckMuteReply) ProtoMessage() {} +func (*CheckMuteReply) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{9} +} + +func (m *CheckMuteReply) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_CheckMuteReply.Unmarshal(m, b) +} +func (m *CheckMuteReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_CheckMuteReply.Marshal(b, m, deterministic) +} +func (m *CheckMuteReply) XXX_Merge(src proto.Message) { + xxx_messageInfo_CheckMuteReply.Merge(m, src) +} +func (m *CheckMuteReply) XXX_Size() int { + return xxx_messageInfo_CheckMuteReply.Size(m) +} +func (m *CheckMuteReply) XXX_DiscardUnknown() { + xxx_messageInfo_CheckMuteReply.DiscardUnknown(m) +} + +var xxx_messageInfo_CheckMuteReply proto.InternalMessageInfo + +func (m *CheckMuteReply) GetIsOk() bool { + if m != nil { + return m.IsOk + } + return false +} + +// 得到加入的所有群 +type GetGroupsReq struct { + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetGroupsReq) Reset() { *m = GetGroupsReq{} } +func (m *GetGroupsReq) String() string { return proto.CompactTextString(m) } +func (*GetGroupsReq) ProtoMessage() {} +func (*GetGroupsReq) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{10} +} + +func (m *GetGroupsReq) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetGroupsReq.Unmarshal(m, b) +} +func (m *GetGroupsReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetGroupsReq.Marshal(b, m, deterministic) +} +func (m *GetGroupsReq) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetGroupsReq.Merge(m, src) +} +func (m *GetGroupsReq) XXX_Size() int { + return xxx_messageInfo_GetGroupsReq.Size(m) +} +func (m *GetGroupsReq) XXX_DiscardUnknown() { + xxx_messageInfo_GetGroupsReq.DiscardUnknown(m) +} + +var xxx_messageInfo_GetGroupsReq proto.InternalMessageInfo + +func (m *GetGroupsReq) GetId() string { + if m != nil { + return m.Id + } + return "" +} + +type GetGroupsResp struct { + Groups []*GroupInfo `protobuf:"bytes,1,rep,name=groups,proto3" json:"groups,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetGroupsResp) Reset() { *m = GetGroupsResp{} } +func (m *GetGroupsResp) String() string { return proto.CompactTextString(m) } +func (*GetGroupsResp) ProtoMessage() {} +func (*GetGroupsResp) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{11} +} + +func (m *GetGroupsResp) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetGroupsResp.Unmarshal(m, b) +} +func (m *GetGroupsResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetGroupsResp.Marshal(b, m, deterministic) +} +func (m *GetGroupsResp) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetGroupsResp.Merge(m, src) +} +func (m *GetGroupsResp) XXX_Size() int { + return xxx_messageInfo_GetGroupsResp.Size(m) +} +func (m *GetGroupsResp) XXX_DiscardUnknown() { + xxx_messageInfo_GetGroupsResp.DiscardUnknown(m) +} + +var xxx_messageInfo_GetGroupsResp proto.InternalMessageInfo + +func (m *GetGroupsResp) GetGroups() []*GroupInfo { + if m != nil { + return m.Groups + } + return nil +} + +type GroupBizInfo struct { + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + MarkId string `protobuf:"bytes,2,opt,name=mark_id,json=markId,proto3" json:"mark_id,omitempty"` + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + Avatar string `protobuf:"bytes,4,opt,name=avatar,proto3" json:"avatar,omitempty"` + MemberNum int32 `protobuf:"varint,5,opt,name=member_num,json=memberNum,proto3" json:"member_num,omitempty"` + MemberMaximum int32 `protobuf:"varint,6,opt,name=member_maximum,json=memberMaximum,proto3" json:"member_maximum,omitempty"` + Introduce string `protobuf:"bytes,7,opt,name=introduce,proto3" json:"introduce,omitempty"` + // 群状态,0=正常 1=封禁 2=解散 + Status GroupStatus `protobuf:"varint,8,opt,name=status,proto3,enum=dtalk.group.GroupStatus" json:"status,omitempty"` + OwnerId string `protobuf:"bytes,9,opt,name=owner_id,json=ownerId,proto3" json:"owner_id,omitempty"` + CreateTime int64 `protobuf:"varint,10,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty"` + UpdateTime int64 `protobuf:"varint,11,opt,name=update_time,json=updateTime,proto3" json:"update_time,omitempty"` + // 加群方式,0=无需审批(默认),1=禁止加群,群主和管理员邀请加群, 2=普通人邀请需要审批,群主和管理员直接加群 + JoinType GroupJoinType `protobuf:"varint,12,opt,name=join_type,json=joinType,proto3,enum=dtalk.group.GroupJoinType" json:"join_type,omitempty"` + // 禁言, 0=全员可发言, 1=全员禁言(除群主和管理员) + MuteType GroupMuteType `protobuf:"varint,13,opt,name=mute_type,json=muteType,proto3,enum=dtalk.group.GroupMuteType" json:"mute_type,omitempty"` + // 加好友限制, 0=群内可加好友,1=群内禁止加好友 + FriendType GroupFriendType `protobuf:"varint,14,opt,name=friend_type,json=friendType,proto3,enum=dtalk.group.GroupFriendType" json:"friend_type,omitempty"` + // 群内当前被禁言的人数 + MuteNum int32 `protobuf:"varint,15,opt,name=mute_num,json=muteNum,proto3" json:"mute_num,omitempty"` + // 群内管理员数量 + AdminNum int32 `protobuf:"varint,16,opt,name=admin_num,json=adminNum,proto3" json:"admin_num,omitempty"` + AESKey string `protobuf:"bytes,17,opt,name=AES_key,json=AESKey,proto3" json:"AES_key,omitempty"` + PubName string `protobuf:"bytes,18,opt,name=pub_name,json=pubName,proto3" json:"pub_name,omitempty"` + // 群类型 (0: 普通群, 1: 全员群, 2: 部门群) + Type GroupType `protobuf:"varint,19,opt,name=type,proto3,enum=dtalk.group.GroupType" json:"type,omitempty"` + Owner *GroupMemberBizInfo `protobuf:"bytes,20,opt,name=owner,proto3" json:"owner,omitempty"` + Person *GroupMemberBizInfo `protobuf:"bytes,21,opt,name=person,proto3" json:"person,omitempty"` + Members []*GroupMemberBizInfo `protobuf:"bytes,22,rep,name=members,proto3" json:"members,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GroupBizInfo) Reset() { *m = GroupBizInfo{} } +func (m *GroupBizInfo) String() string { return proto.CompactTextString(m) } +func (*GroupBizInfo) ProtoMessage() {} +func (*GroupBizInfo) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{12} +} + +func (m *GroupBizInfo) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GroupBizInfo.Unmarshal(m, b) +} +func (m *GroupBizInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GroupBizInfo.Marshal(b, m, deterministic) +} +func (m *GroupBizInfo) XXX_Merge(src proto.Message) { + xxx_messageInfo_GroupBizInfo.Merge(m, src) +} +func (m *GroupBizInfo) XXX_Size() int { + return xxx_messageInfo_GroupBizInfo.Size(m) +} +func (m *GroupBizInfo) XXX_DiscardUnknown() { + xxx_messageInfo_GroupBizInfo.DiscardUnknown(m) +} + +var xxx_messageInfo_GroupBizInfo proto.InternalMessageInfo + +func (m *GroupBizInfo) GetId() int64 { + if m != nil { + return m.Id + } + return 0 +} + +func (m *GroupBizInfo) GetMarkId() string { + if m != nil { + return m.MarkId + } + return "" +} + +func (m *GroupBizInfo) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *GroupBizInfo) GetAvatar() string { + if m != nil { + return m.Avatar + } + return "" +} + +func (m *GroupBizInfo) GetMemberNum() int32 { + if m != nil { + return m.MemberNum + } + return 0 +} + +func (m *GroupBizInfo) GetMemberMaximum() int32 { + if m != nil { + return m.MemberMaximum + } + return 0 +} + +func (m *GroupBizInfo) GetIntroduce() string { + if m != nil { + return m.Introduce + } + return "" +} + +func (m *GroupBizInfo) GetStatus() GroupStatus { + if m != nil { + return m.Status + } + return GroupStatus_GROUP_STATUS_NORMAL +} + +func (m *GroupBizInfo) GetOwnerId() string { + if m != nil { + return m.OwnerId + } + return "" +} + +func (m *GroupBizInfo) GetCreateTime() int64 { + if m != nil { + return m.CreateTime + } + return 0 +} + +func (m *GroupBizInfo) GetUpdateTime() int64 { + if m != nil { + return m.UpdateTime + } + return 0 +} + +func (m *GroupBizInfo) GetJoinType() GroupJoinType { + if m != nil { + return m.JoinType + } + return GroupJoinType_GROUP_JOIN_TYPE_ANY +} + +func (m *GroupBizInfo) GetMuteType() GroupMuteType { + if m != nil { + return m.MuteType + } + return GroupMuteType_GROUP_MUTE_TYPE_ANY +} + +func (m *GroupBizInfo) GetFriendType() GroupFriendType { + if m != nil { + return m.FriendType + } + return GroupFriendType_GROUP_FRIEND_TYPE_ALLOW +} + +func (m *GroupBizInfo) GetMuteNum() int32 { + if m != nil { + return m.MuteNum + } + return 0 +} + +func (m *GroupBizInfo) GetAdminNum() int32 { + if m != nil { + return m.AdminNum + } + return 0 +} + +func (m *GroupBizInfo) GetAESKey() string { + if m != nil { + return m.AESKey + } + return "" +} + +func (m *GroupBizInfo) GetPubName() string { + if m != nil { + return m.PubName + } + return "" +} + +func (m *GroupBizInfo) GetType() GroupType { + if m != nil { + return m.Type + } + return GroupType_GROUP_TYPE_NORMAL +} + +func (m *GroupBizInfo) GetOwner() *GroupMemberBizInfo { + if m != nil { + return m.Owner + } + return nil +} + +func (m *GroupBizInfo) GetPerson() *GroupMemberBizInfo { + if m != nil { + return m.Person + } + return nil +} + +func (m *GroupBizInfo) GetMembers() []*GroupMemberBizInfo { + if m != nil { + return m.Members + } + return nil +} + +type GroupMemberBizInfo struct { + GroupId int64 `protobuf:"varint,1,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"` + Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + // 用户角色,0=群员,1=管理员, 2=群主,10=退群 + Type GroupMemberType `protobuf:"varint,4,opt,name=type,proto3,enum=dtalk.group.GroupMemberType" json:"type,omitempty"` + // 该用户被禁言结束的时间 9223372036854775807=永久禁言 + MuteTime int64 `protobuf:"varint,5,opt,name=mute_time,json=muteTime,proto3" json:"mute_time,omitempty"` + JoinTime int64 `protobuf:"varint,6,opt,name=join_time,json=joinTime,proto3" json:"join_time,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GroupMemberBizInfo) Reset() { *m = GroupMemberBizInfo{} } +func (m *GroupMemberBizInfo) String() string { return proto.CompactTextString(m) } +func (*GroupMemberBizInfo) ProtoMessage() {} +func (*GroupMemberBizInfo) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{13} +} + +func (m *GroupMemberBizInfo) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GroupMemberBizInfo.Unmarshal(m, b) +} +func (m *GroupMemberBizInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GroupMemberBizInfo.Marshal(b, m, deterministic) +} +func (m *GroupMemberBizInfo) XXX_Merge(src proto.Message) { + xxx_messageInfo_GroupMemberBizInfo.Merge(m, src) +} +func (m *GroupMemberBizInfo) XXX_Size() int { + return xxx_messageInfo_GroupMemberBizInfo.Size(m) +} +func (m *GroupMemberBizInfo) XXX_DiscardUnknown() { + xxx_messageInfo_GroupMemberBizInfo.DiscardUnknown(m) +} + +var xxx_messageInfo_GroupMemberBizInfo proto.InternalMessageInfo + +func (m *GroupMemberBizInfo) GetGroupId() int64 { + if m != nil { + return m.GroupId + } + return 0 +} + +func (m *GroupMemberBizInfo) GetId() string { + if m != nil { + return m.Id + } + return "" +} + +func (m *GroupMemberBizInfo) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *GroupMemberBizInfo) GetType() GroupMemberType { + if m != nil { + return m.Type + } + return GroupMemberType_GROUP_MEMBER_TYPE_NORMAL +} + +func (m *GroupMemberBizInfo) GetMuteTime() int64 { + if m != nil { + return m.MuteTime + } + return 0 +} + +func (m *GroupMemberBizInfo) GetJoinTime() int64 { + if m != nil { + return m.JoinTime + } + return 0 +} + +// 创建群聊 +type CreateGroupReq struct { + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + GroupType GroupType `protobuf:"varint,2,opt,name=group_type,json=groupType,proto3,enum=dtalk.group.GroupType" json:"group_type,omitempty"` + Owner *GroupMemberInfo `protobuf:"bytes,3,opt,name=owner,proto3" json:"owner,omitempty"` + Members []*GroupMemberInfo `protobuf:"bytes,4,rep,name=members,proto3" json:"members,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *CreateGroupReq) Reset() { *m = CreateGroupReq{} } +func (m *CreateGroupReq) String() string { return proto.CompactTextString(m) } +func (*CreateGroupReq) ProtoMessage() {} +func (*CreateGroupReq) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{14} +} + +func (m *CreateGroupReq) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_CreateGroupReq.Unmarshal(m, b) +} +func (m *CreateGroupReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_CreateGroupReq.Marshal(b, m, deterministic) +} +func (m *CreateGroupReq) XXX_Merge(src proto.Message) { + xxx_messageInfo_CreateGroupReq.Merge(m, src) +} +func (m *CreateGroupReq) XXX_Size() int { + return xxx_messageInfo_CreateGroupReq.Size(m) +} +func (m *CreateGroupReq) XXX_DiscardUnknown() { + xxx_messageInfo_CreateGroupReq.DiscardUnknown(m) +} + +var xxx_messageInfo_CreateGroupReq proto.InternalMessageInfo + +func (m *CreateGroupReq) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *CreateGroupReq) GetGroupType() GroupType { + if m != nil { + return m.GroupType + } + return GroupType_GROUP_TYPE_NORMAL +} + +func (m *CreateGroupReq) GetOwner() *GroupMemberInfo { + if m != nil { + return m.Owner + } + return nil +} + +func (m *CreateGroupReq) GetMembers() []*GroupMemberInfo { + if m != nil { + return m.Members + } + return nil +} + +type CreateGroupResp struct { + GroupId int64 `protobuf:"varint,1,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *CreateGroupResp) Reset() { *m = CreateGroupResp{} } +func (m *CreateGroupResp) String() string { return proto.CompactTextString(m) } +func (*CreateGroupResp) ProtoMessage() {} +func (*CreateGroupResp) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{15} +} + +func (m *CreateGroupResp) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_CreateGroupResp.Unmarshal(m, b) +} +func (m *CreateGroupResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_CreateGroupResp.Marshal(b, m, deterministic) +} +func (m *CreateGroupResp) XXX_Merge(src proto.Message) { + xxx_messageInfo_CreateGroupResp.Merge(m, src) +} +func (m *CreateGroupResp) XXX_Size() int { + return xxx_messageInfo_CreateGroupResp.Size(m) +} +func (m *CreateGroupResp) XXX_DiscardUnknown() { + xxx_messageInfo_CreateGroupResp.DiscardUnknown(m) +} + +var xxx_messageInfo_CreateGroupResp proto.InternalMessageInfo + +func (m *CreateGroupResp) GetGroupId() int64 { + if m != nil { + return m.GroupId + } + return 0 +} + +type InviteGroupMembersReq struct { + GroupId int64 `protobuf:"varint,1,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"` + InviterId string `protobuf:"bytes,2,opt,name=inviter_id,json=inviterId,proto3" json:"inviter_id,omitempty"` + MemberIds []string `protobuf:"bytes,3,rep,name=member_ids,json=memberIds,proto3" json:"member_ids,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *InviteGroupMembersReq) Reset() { *m = InviteGroupMembersReq{} } +func (m *InviteGroupMembersReq) String() string { return proto.CompactTextString(m) } +func (*InviteGroupMembersReq) ProtoMessage() {} +func (*InviteGroupMembersReq) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{16} +} + +func (m *InviteGroupMembersReq) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_InviteGroupMembersReq.Unmarshal(m, b) +} +func (m *InviteGroupMembersReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_InviteGroupMembersReq.Marshal(b, m, deterministic) +} +func (m *InviteGroupMembersReq) XXX_Merge(src proto.Message) { + xxx_messageInfo_InviteGroupMembersReq.Merge(m, src) +} +func (m *InviteGroupMembersReq) XXX_Size() int { + return xxx_messageInfo_InviteGroupMembersReq.Size(m) +} +func (m *InviteGroupMembersReq) XXX_DiscardUnknown() { + xxx_messageInfo_InviteGroupMembersReq.DiscardUnknown(m) +} + +var xxx_messageInfo_InviteGroupMembersReq proto.InternalMessageInfo + +func (m *InviteGroupMembersReq) GetGroupId() int64 { + if m != nil { + return m.GroupId + } + return 0 +} + +func (m *InviteGroupMembersReq) GetInviterId() string { + if m != nil { + return m.InviterId + } + return "" +} + +func (m *InviteGroupMembersReq) GetMemberIds() []string { + if m != nil { + return m.MemberIds + } + return nil +} + +type InviteGroupMembersResp struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *InviteGroupMembersResp) Reset() { *m = InviteGroupMembersResp{} } +func (m *InviteGroupMembersResp) String() string { return proto.CompactTextString(m) } +func (*InviteGroupMembersResp) ProtoMessage() {} +func (*InviteGroupMembersResp) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{17} +} + +func (m *InviteGroupMembersResp) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_InviteGroupMembersResp.Unmarshal(m, b) +} +func (m *InviteGroupMembersResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_InviteGroupMembersResp.Marshal(b, m, deterministic) +} +func (m *InviteGroupMembersResp) XXX_Merge(src proto.Message) { + xxx_messageInfo_InviteGroupMembersResp.Merge(m, src) +} +func (m *InviteGroupMembersResp) XXX_Size() int { + return xxx_messageInfo_InviteGroupMembersResp.Size(m) +} +func (m *InviteGroupMembersResp) XXX_DiscardUnknown() { + xxx_messageInfo_InviteGroupMembersResp.DiscardUnknown(m) +} + +var xxx_messageInfo_InviteGroupMembersResp proto.InternalMessageInfo + +type GroupExitReq struct { + GroupId int64 `protobuf:"varint,1,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"` + PersonId string `protobuf:"bytes,2,opt,name=person_id,json=personId,proto3" json:"person_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GroupExitReq) Reset() { *m = GroupExitReq{} } +func (m *GroupExitReq) String() string { return proto.CompactTextString(m) } +func (*GroupExitReq) ProtoMessage() {} +func (*GroupExitReq) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{18} +} + +func (m *GroupExitReq) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GroupExitReq.Unmarshal(m, b) +} +func (m *GroupExitReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GroupExitReq.Marshal(b, m, deterministic) +} +func (m *GroupExitReq) XXX_Merge(src proto.Message) { + xxx_messageInfo_GroupExitReq.Merge(m, src) +} +func (m *GroupExitReq) XXX_Size() int { + return xxx_messageInfo_GroupExitReq.Size(m) +} +func (m *GroupExitReq) XXX_DiscardUnknown() { + xxx_messageInfo_GroupExitReq.DiscardUnknown(m) +} + +var xxx_messageInfo_GroupExitReq proto.InternalMessageInfo + +func (m *GroupExitReq) GetGroupId() int64 { + if m != nil { + return m.GroupId + } + return 0 +} + +func (m *GroupExitReq) GetPersonId() string { + if m != nil { + return m.PersonId + } + return "" +} + +type GroupExitResp struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GroupExitResp) Reset() { *m = GroupExitResp{} } +func (m *GroupExitResp) String() string { return proto.CompactTextString(m) } +func (*GroupExitResp) ProtoMessage() {} +func (*GroupExitResp) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{19} +} + +func (m *GroupExitResp) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GroupExitResp.Unmarshal(m, b) +} +func (m *GroupExitResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GroupExitResp.Marshal(b, m, deterministic) +} +func (m *GroupExitResp) XXX_Merge(src proto.Message) { + xxx_messageInfo_GroupExitResp.Merge(m, src) +} +func (m *GroupExitResp) XXX_Size() int { + return xxx_messageInfo_GroupExitResp.Size(m) +} +func (m *GroupExitResp) XXX_DiscardUnknown() { + xxx_messageInfo_GroupExitResp.DiscardUnknown(m) +} + +var xxx_messageInfo_GroupExitResp proto.InternalMessageInfo + +type GroupDisbandReq struct { + GroupId int64 `protobuf:"varint,1,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"` + PersonId string `protobuf:"bytes,2,opt,name=person_id,json=personId,proto3" json:"person_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GroupDisbandReq) Reset() { *m = GroupDisbandReq{} } +func (m *GroupDisbandReq) String() string { return proto.CompactTextString(m) } +func (*GroupDisbandReq) ProtoMessage() {} +func (*GroupDisbandReq) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{20} +} + +func (m *GroupDisbandReq) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GroupDisbandReq.Unmarshal(m, b) +} +func (m *GroupDisbandReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GroupDisbandReq.Marshal(b, m, deterministic) +} +func (m *GroupDisbandReq) XXX_Merge(src proto.Message) { + xxx_messageInfo_GroupDisbandReq.Merge(m, src) +} +func (m *GroupDisbandReq) XXX_Size() int { + return xxx_messageInfo_GroupDisbandReq.Size(m) +} +func (m *GroupDisbandReq) XXX_DiscardUnknown() { + xxx_messageInfo_GroupDisbandReq.DiscardUnknown(m) +} + +var xxx_messageInfo_GroupDisbandReq proto.InternalMessageInfo + +func (m *GroupDisbandReq) GetGroupId() int64 { + if m != nil { + return m.GroupId + } + return 0 +} + +func (m *GroupDisbandReq) GetPersonId() string { + if m != nil { + return m.PersonId + } + return "" +} + +type GroupDisbandResp struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GroupDisbandResp) Reset() { *m = GroupDisbandResp{} } +func (m *GroupDisbandResp) String() string { return proto.CompactTextString(m) } +func (*GroupDisbandResp) ProtoMessage() {} +func (*GroupDisbandResp) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{21} +} + +func (m *GroupDisbandResp) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GroupDisbandResp.Unmarshal(m, b) +} +func (m *GroupDisbandResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GroupDisbandResp.Marshal(b, m, deterministic) +} +func (m *GroupDisbandResp) XXX_Merge(src proto.Message) { + xxx_messageInfo_GroupDisbandResp.Merge(m, src) +} +func (m *GroupDisbandResp) XXX_Size() int { + return xxx_messageInfo_GroupDisbandResp.Size(m) +} +func (m *GroupDisbandResp) XXX_DiscardUnknown() { + xxx_messageInfo_GroupDisbandResp.DiscardUnknown(m) +} + +var xxx_messageInfo_GroupDisbandResp proto.InternalMessageInfo + +type GroupRemoveReq struct { + GroupId int64 `protobuf:"varint,1,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"` + PersonId string `protobuf:"bytes,2,opt,name=person_id,json=personId,proto3" json:"person_id,omitempty"` + MemberIds []string `protobuf:"bytes,3,rep,name=member_ids,json=memberIds,proto3" json:"member_ids,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GroupRemoveReq) Reset() { *m = GroupRemoveReq{} } +func (m *GroupRemoveReq) String() string { return proto.CompactTextString(m) } +func (*GroupRemoveReq) ProtoMessage() {} +func (*GroupRemoveReq) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{22} +} + +func (m *GroupRemoveReq) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GroupRemoveReq.Unmarshal(m, b) +} +func (m *GroupRemoveReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GroupRemoveReq.Marshal(b, m, deterministic) +} +func (m *GroupRemoveReq) XXX_Merge(src proto.Message) { + xxx_messageInfo_GroupRemoveReq.Merge(m, src) +} +func (m *GroupRemoveReq) XXX_Size() int { + return xxx_messageInfo_GroupRemoveReq.Size(m) +} +func (m *GroupRemoveReq) XXX_DiscardUnknown() { + xxx_messageInfo_GroupRemoveReq.DiscardUnknown(m) +} + +var xxx_messageInfo_GroupRemoveReq proto.InternalMessageInfo + +func (m *GroupRemoveReq) GetGroupId() int64 { + if m != nil { + return m.GroupId + } + return 0 +} + +func (m *GroupRemoveReq) GetPersonId() string { + if m != nil { + return m.PersonId + } + return "" +} + +func (m *GroupRemoveReq) GetMemberIds() []string { + if m != nil { + return m.MemberIds + } + return nil +} + +type GroupRemoveResp struct { + // 群当前人数 + MemberNum int32 `protobuf:"varint,1,opt,name=member_num,json=memberNum,proto3" json:"member_num,omitempty"` + // 成功被踢的成员列表 + MemberIds []string `protobuf:"bytes,2,rep,name=member_ids,json=memberIds,proto3" json:"member_ids,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GroupRemoveResp) Reset() { *m = GroupRemoveResp{} } +func (m *GroupRemoveResp) String() string { return proto.CompactTextString(m) } +func (*GroupRemoveResp) ProtoMessage() {} +func (*GroupRemoveResp) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{23} +} + +func (m *GroupRemoveResp) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GroupRemoveResp.Unmarshal(m, b) +} +func (m *GroupRemoveResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GroupRemoveResp.Marshal(b, m, deterministic) +} +func (m *GroupRemoveResp) XXX_Merge(src proto.Message) { + xxx_messageInfo_GroupRemoveResp.Merge(m, src) +} +func (m *GroupRemoveResp) XXX_Size() int { + return xxx_messageInfo_GroupRemoveResp.Size(m) +} +func (m *GroupRemoveResp) XXX_DiscardUnknown() { + xxx_messageInfo_GroupRemoveResp.DiscardUnknown(m) +} + +var xxx_messageInfo_GroupRemoveResp proto.InternalMessageInfo + +func (m *GroupRemoveResp) GetMemberNum() int32 { + if m != nil { + return m.MemberNum + } + return 0 +} + +func (m *GroupRemoveResp) GetMemberIds() []string { + if m != nil { + return m.MemberIds + } + return nil +} + +type ChangeOwnerReq struct { + GroupId int64 `protobuf:"varint,1,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"` + PersonId string `protobuf:"bytes,2,opt,name=person_id,json=personId,proto3" json:"person_id,omitempty"` + // 被转让为群主的群成员 id + MemberId string `protobuf:"bytes,3,opt,name=member_id,json=memberId,proto3" json:"member_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ChangeOwnerReq) Reset() { *m = ChangeOwnerReq{} } +func (m *ChangeOwnerReq) String() string { return proto.CompactTextString(m) } +func (*ChangeOwnerReq) ProtoMessage() {} +func (*ChangeOwnerReq) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{24} +} + +func (m *ChangeOwnerReq) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ChangeOwnerReq.Unmarshal(m, b) +} +func (m *ChangeOwnerReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ChangeOwnerReq.Marshal(b, m, deterministic) +} +func (m *ChangeOwnerReq) XXX_Merge(src proto.Message) { + xxx_messageInfo_ChangeOwnerReq.Merge(m, src) +} +func (m *ChangeOwnerReq) XXX_Size() int { + return xxx_messageInfo_ChangeOwnerReq.Size(m) +} +func (m *ChangeOwnerReq) XXX_DiscardUnknown() { + xxx_messageInfo_ChangeOwnerReq.DiscardUnknown(m) +} + +var xxx_messageInfo_ChangeOwnerReq proto.InternalMessageInfo + +func (m *ChangeOwnerReq) GetGroupId() int64 { + if m != nil { + return m.GroupId + } + return 0 +} + +func (m *ChangeOwnerReq) GetPersonId() string { + if m != nil { + return m.PersonId + } + return "" +} + +func (m *ChangeOwnerReq) GetMemberId() string { + if m != nil { + return m.MemberId + } + return "" +} + +type ChangeOwnerResp struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ChangeOwnerResp) Reset() { *m = ChangeOwnerResp{} } +func (m *ChangeOwnerResp) String() string { return proto.CompactTextString(m) } +func (*ChangeOwnerResp) ProtoMessage() {} +func (*ChangeOwnerResp) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{25} +} + +func (m *ChangeOwnerResp) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ChangeOwnerResp.Unmarshal(m, b) +} +func (m *ChangeOwnerResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ChangeOwnerResp.Marshal(b, m, deterministic) +} +func (m *ChangeOwnerResp) XXX_Merge(src proto.Message) { + xxx_messageInfo_ChangeOwnerResp.Merge(m, src) +} +func (m *ChangeOwnerResp) XXX_Size() int { + return xxx_messageInfo_ChangeOwnerResp.Size(m) +} +func (m *ChangeOwnerResp) XXX_DiscardUnknown() { + xxx_messageInfo_ChangeOwnerResp.DiscardUnknown(m) +} + +var xxx_messageInfo_ChangeOwnerResp proto.InternalMessageInfo + +type UpdateGroupNameReq struct { + GroupId int64 `protobuf:"varint,1,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"` + PersonId string `protobuf:"bytes,2,opt,name=person_id,json=personId,proto3" json:"person_id,omitempty"` + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + PublicName string `protobuf:"bytes,4,opt,name=public_name,json=publicName,proto3" json:"public_name,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *UpdateGroupNameReq) Reset() { *m = UpdateGroupNameReq{} } +func (m *UpdateGroupNameReq) String() string { return proto.CompactTextString(m) } +func (*UpdateGroupNameReq) ProtoMessage() {} +func (*UpdateGroupNameReq) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{26} +} + +func (m *UpdateGroupNameReq) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_UpdateGroupNameReq.Unmarshal(m, b) +} +func (m *UpdateGroupNameReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_UpdateGroupNameReq.Marshal(b, m, deterministic) +} +func (m *UpdateGroupNameReq) XXX_Merge(src proto.Message) { + xxx_messageInfo_UpdateGroupNameReq.Merge(m, src) +} +func (m *UpdateGroupNameReq) XXX_Size() int { + return xxx_messageInfo_UpdateGroupNameReq.Size(m) +} +func (m *UpdateGroupNameReq) XXX_DiscardUnknown() { + xxx_messageInfo_UpdateGroupNameReq.DiscardUnknown(m) +} + +var xxx_messageInfo_UpdateGroupNameReq proto.InternalMessageInfo + +func (m *UpdateGroupNameReq) GetGroupId() int64 { + if m != nil { + return m.GroupId + } + return 0 +} + +func (m *UpdateGroupNameReq) GetPersonId() string { + if m != nil { + return m.PersonId + } + return "" +} + +func (m *UpdateGroupNameReq) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *UpdateGroupNameReq) GetPublicName() string { + if m != nil { + return m.PublicName + } + return "" +} + +type UpdateGroupNameResp struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *UpdateGroupNameResp) Reset() { *m = UpdateGroupNameResp{} } +func (m *UpdateGroupNameResp) String() string { return proto.CompactTextString(m) } +func (*UpdateGroupNameResp) ProtoMessage() {} +func (*UpdateGroupNameResp) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{27} +} + +func (m *UpdateGroupNameResp) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_UpdateGroupNameResp.Unmarshal(m, b) +} +func (m *UpdateGroupNameResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_UpdateGroupNameResp.Marshal(b, m, deterministic) +} +func (m *UpdateGroupNameResp) XXX_Merge(src proto.Message) { + xxx_messageInfo_UpdateGroupNameResp.Merge(m, src) +} +func (m *UpdateGroupNameResp) XXX_Size() int { + return xxx_messageInfo_UpdateGroupNameResp.Size(m) +} +func (m *UpdateGroupNameResp) XXX_DiscardUnknown() { + xxx_messageInfo_UpdateGroupNameResp.DiscardUnknown(m) +} + +var xxx_messageInfo_UpdateGroupNameResp proto.InternalMessageInfo + +type UpdateGroupAvatarReq struct { + GroupId int64 `protobuf:"varint,1,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"` + PersonId string `protobuf:"bytes,2,opt,name=person_id,json=personId,proto3" json:"person_id,omitempty"` + // 群头像 url + Avatar string `protobuf:"bytes,3,opt,name=avatar,proto3" json:"avatar,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *UpdateGroupAvatarReq) Reset() { *m = UpdateGroupAvatarReq{} } +func (m *UpdateGroupAvatarReq) String() string { return proto.CompactTextString(m) } +func (*UpdateGroupAvatarReq) ProtoMessage() {} +func (*UpdateGroupAvatarReq) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{28} +} + +func (m *UpdateGroupAvatarReq) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_UpdateGroupAvatarReq.Unmarshal(m, b) +} +func (m *UpdateGroupAvatarReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_UpdateGroupAvatarReq.Marshal(b, m, deterministic) +} +func (m *UpdateGroupAvatarReq) XXX_Merge(src proto.Message) { + xxx_messageInfo_UpdateGroupAvatarReq.Merge(m, src) +} +func (m *UpdateGroupAvatarReq) XXX_Size() int { + return xxx_messageInfo_UpdateGroupAvatarReq.Size(m) +} +func (m *UpdateGroupAvatarReq) XXX_DiscardUnknown() { + xxx_messageInfo_UpdateGroupAvatarReq.DiscardUnknown(m) +} + +var xxx_messageInfo_UpdateGroupAvatarReq proto.InternalMessageInfo + +func (m *UpdateGroupAvatarReq) GetGroupId() int64 { + if m != nil { + return m.GroupId + } + return 0 +} + +func (m *UpdateGroupAvatarReq) GetPersonId() string { + if m != nil { + return m.PersonId + } + return "" +} + +func (m *UpdateGroupAvatarReq) GetAvatar() string { + if m != nil { + return m.Avatar + } + return "" +} + +type UpdateGroupAvatarResp struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *UpdateGroupAvatarResp) Reset() { *m = UpdateGroupAvatarResp{} } +func (m *UpdateGroupAvatarResp) String() string { return proto.CompactTextString(m) } +func (*UpdateGroupAvatarResp) ProtoMessage() {} +func (*UpdateGroupAvatarResp) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{29} +} + +func (m *UpdateGroupAvatarResp) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_UpdateGroupAvatarResp.Unmarshal(m, b) +} +func (m *UpdateGroupAvatarResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_UpdateGroupAvatarResp.Marshal(b, m, deterministic) +} +func (m *UpdateGroupAvatarResp) XXX_Merge(src proto.Message) { + xxx_messageInfo_UpdateGroupAvatarResp.Merge(m, src) +} +func (m *UpdateGroupAvatarResp) XXX_Size() int { + return xxx_messageInfo_UpdateGroupAvatarResp.Size(m) +} +func (m *UpdateGroupAvatarResp) XXX_DiscardUnknown() { + xxx_messageInfo_UpdateGroupAvatarResp.DiscardUnknown(m) +} + +var xxx_messageInfo_UpdateGroupAvatarResp proto.InternalMessageInfo + +type UpdateGroupJoinTypeReq struct { + GroupId int64 `protobuf:"varint,1,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"` + PersonId string `protobuf:"bytes,2,opt,name=person_id,json=personId,proto3" json:"person_id,omitempty"` + GroupJoinType GroupJoinType `protobuf:"varint,3,opt,name=group_join_type,json=groupJoinType,proto3,enum=dtalk.group.GroupJoinType" json:"group_join_type,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *UpdateGroupJoinTypeReq) Reset() { *m = UpdateGroupJoinTypeReq{} } +func (m *UpdateGroupJoinTypeReq) String() string { return proto.CompactTextString(m) } +func (*UpdateGroupJoinTypeReq) ProtoMessage() {} +func (*UpdateGroupJoinTypeReq) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{30} +} + +func (m *UpdateGroupJoinTypeReq) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_UpdateGroupJoinTypeReq.Unmarshal(m, b) +} +func (m *UpdateGroupJoinTypeReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_UpdateGroupJoinTypeReq.Marshal(b, m, deterministic) +} +func (m *UpdateGroupJoinTypeReq) XXX_Merge(src proto.Message) { + xxx_messageInfo_UpdateGroupJoinTypeReq.Merge(m, src) +} +func (m *UpdateGroupJoinTypeReq) XXX_Size() int { + return xxx_messageInfo_UpdateGroupJoinTypeReq.Size(m) +} +func (m *UpdateGroupJoinTypeReq) XXX_DiscardUnknown() { + xxx_messageInfo_UpdateGroupJoinTypeReq.DiscardUnknown(m) +} + +var xxx_messageInfo_UpdateGroupJoinTypeReq proto.InternalMessageInfo + +func (m *UpdateGroupJoinTypeReq) GetGroupId() int64 { + if m != nil { + return m.GroupId + } + return 0 +} + +func (m *UpdateGroupJoinTypeReq) GetPersonId() string { + if m != nil { + return m.PersonId + } + return "" +} + +func (m *UpdateGroupJoinTypeReq) GetGroupJoinType() GroupJoinType { + if m != nil { + return m.GroupJoinType + } + return GroupJoinType_GROUP_JOIN_TYPE_ANY +} + +type UpdateGroupJoinTypeResp struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *UpdateGroupJoinTypeResp) Reset() { *m = UpdateGroupJoinTypeResp{} } +func (m *UpdateGroupJoinTypeResp) String() string { return proto.CompactTextString(m) } +func (*UpdateGroupJoinTypeResp) ProtoMessage() {} +func (*UpdateGroupJoinTypeResp) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{31} +} + +func (m *UpdateGroupJoinTypeResp) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_UpdateGroupJoinTypeResp.Unmarshal(m, b) +} +func (m *UpdateGroupJoinTypeResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_UpdateGroupJoinTypeResp.Marshal(b, m, deterministic) +} +func (m *UpdateGroupJoinTypeResp) XXX_Merge(src proto.Message) { + xxx_messageInfo_UpdateGroupJoinTypeResp.Merge(m, src) +} +func (m *UpdateGroupJoinTypeResp) XXX_Size() int { + return xxx_messageInfo_UpdateGroupJoinTypeResp.Size(m) +} +func (m *UpdateGroupJoinTypeResp) XXX_DiscardUnknown() { + xxx_messageInfo_UpdateGroupJoinTypeResp.DiscardUnknown(m) +} + +var xxx_messageInfo_UpdateGroupJoinTypeResp proto.InternalMessageInfo + +type UpdateGroupFriendTypeReq struct { + GroupId int64 `protobuf:"varint,1,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"` + PersonId string `protobuf:"bytes,2,opt,name=person_id,json=personId,proto3" json:"person_id,omitempty"` + GroupFriendType GroupFriendType `protobuf:"varint,3,opt,name=group_friend_type,json=groupFriendType,proto3,enum=dtalk.group.GroupFriendType" json:"group_friend_type,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *UpdateGroupFriendTypeReq) Reset() { *m = UpdateGroupFriendTypeReq{} } +func (m *UpdateGroupFriendTypeReq) String() string { return proto.CompactTextString(m) } +func (*UpdateGroupFriendTypeReq) ProtoMessage() {} +func (*UpdateGroupFriendTypeReq) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{32} +} + +func (m *UpdateGroupFriendTypeReq) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_UpdateGroupFriendTypeReq.Unmarshal(m, b) +} +func (m *UpdateGroupFriendTypeReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_UpdateGroupFriendTypeReq.Marshal(b, m, deterministic) +} +func (m *UpdateGroupFriendTypeReq) XXX_Merge(src proto.Message) { + xxx_messageInfo_UpdateGroupFriendTypeReq.Merge(m, src) +} +func (m *UpdateGroupFriendTypeReq) XXX_Size() int { + return xxx_messageInfo_UpdateGroupFriendTypeReq.Size(m) +} +func (m *UpdateGroupFriendTypeReq) XXX_DiscardUnknown() { + xxx_messageInfo_UpdateGroupFriendTypeReq.DiscardUnknown(m) +} + +var xxx_messageInfo_UpdateGroupFriendTypeReq proto.InternalMessageInfo + +func (m *UpdateGroupFriendTypeReq) GetGroupId() int64 { + if m != nil { + return m.GroupId + } + return 0 +} + +func (m *UpdateGroupFriendTypeReq) GetPersonId() string { + if m != nil { + return m.PersonId + } + return "" +} + +func (m *UpdateGroupFriendTypeReq) GetGroupFriendType() GroupFriendType { + if m != nil { + return m.GroupFriendType + } + return GroupFriendType_GROUP_FRIEND_TYPE_ALLOW +} + +type UpdateGroupFriendTypeResp struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *UpdateGroupFriendTypeResp) Reset() { *m = UpdateGroupFriendTypeResp{} } +func (m *UpdateGroupFriendTypeResp) String() string { return proto.CompactTextString(m) } +func (*UpdateGroupFriendTypeResp) ProtoMessage() {} +func (*UpdateGroupFriendTypeResp) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{33} +} + +func (m *UpdateGroupFriendTypeResp) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_UpdateGroupFriendTypeResp.Unmarshal(m, b) +} +func (m *UpdateGroupFriendTypeResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_UpdateGroupFriendTypeResp.Marshal(b, m, deterministic) +} +func (m *UpdateGroupFriendTypeResp) XXX_Merge(src proto.Message) { + xxx_messageInfo_UpdateGroupFriendTypeResp.Merge(m, src) +} +func (m *UpdateGroupFriendTypeResp) XXX_Size() int { + return xxx_messageInfo_UpdateGroupFriendTypeResp.Size(m) +} +func (m *UpdateGroupFriendTypeResp) XXX_DiscardUnknown() { + xxx_messageInfo_UpdateGroupFriendTypeResp.DiscardUnknown(m) +} + +var xxx_messageInfo_UpdateGroupFriendTypeResp proto.InternalMessageInfo + +type UpdateGroupMuteTypeReq struct { + GroupId int64 `protobuf:"varint,1,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"` + PersonId string `protobuf:"bytes,2,opt,name=person_id,json=personId,proto3" json:"person_id,omitempty"` + GroupMuteType GroupMuteType `protobuf:"varint,3,opt,name=group_mute_type,json=groupMuteType,proto3,enum=dtalk.group.GroupMuteType" json:"group_mute_type,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *UpdateGroupMuteTypeReq) Reset() { *m = UpdateGroupMuteTypeReq{} } +func (m *UpdateGroupMuteTypeReq) String() string { return proto.CompactTextString(m) } +func (*UpdateGroupMuteTypeReq) ProtoMessage() {} +func (*UpdateGroupMuteTypeReq) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{34} +} + +func (m *UpdateGroupMuteTypeReq) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_UpdateGroupMuteTypeReq.Unmarshal(m, b) +} +func (m *UpdateGroupMuteTypeReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_UpdateGroupMuteTypeReq.Marshal(b, m, deterministic) +} +func (m *UpdateGroupMuteTypeReq) XXX_Merge(src proto.Message) { + xxx_messageInfo_UpdateGroupMuteTypeReq.Merge(m, src) +} +func (m *UpdateGroupMuteTypeReq) XXX_Size() int { + return xxx_messageInfo_UpdateGroupMuteTypeReq.Size(m) +} +func (m *UpdateGroupMuteTypeReq) XXX_DiscardUnknown() { + xxx_messageInfo_UpdateGroupMuteTypeReq.DiscardUnknown(m) +} + +var xxx_messageInfo_UpdateGroupMuteTypeReq proto.InternalMessageInfo + +func (m *UpdateGroupMuteTypeReq) GetGroupId() int64 { + if m != nil { + return m.GroupId + } + return 0 +} + +func (m *UpdateGroupMuteTypeReq) GetPersonId() string { + if m != nil { + return m.PersonId + } + return "" +} + +func (m *UpdateGroupMuteTypeReq) GetGroupMuteType() GroupMuteType { + if m != nil { + return m.GroupMuteType + } + return GroupMuteType_GROUP_MUTE_TYPE_ANY +} + +type UpdateGroupMuteTypeResp struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *UpdateGroupMuteTypeResp) Reset() { *m = UpdateGroupMuteTypeResp{} } +func (m *UpdateGroupMuteTypeResp) String() string { return proto.CompactTextString(m) } +func (*UpdateGroupMuteTypeResp) ProtoMessage() {} +func (*UpdateGroupMuteTypeResp) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{35} +} + +func (m *UpdateGroupMuteTypeResp) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_UpdateGroupMuteTypeResp.Unmarshal(m, b) +} +func (m *UpdateGroupMuteTypeResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_UpdateGroupMuteTypeResp.Marshal(b, m, deterministic) +} +func (m *UpdateGroupMuteTypeResp) XXX_Merge(src proto.Message) { + xxx_messageInfo_UpdateGroupMuteTypeResp.Merge(m, src) +} +func (m *UpdateGroupMuteTypeResp) XXX_Size() int { + return xxx_messageInfo_UpdateGroupMuteTypeResp.Size(m) +} +func (m *UpdateGroupMuteTypeResp) XXX_DiscardUnknown() { + xxx_messageInfo_UpdateGroupMuteTypeResp.DiscardUnknown(m) +} + +var xxx_messageInfo_UpdateGroupMuteTypeResp proto.InternalMessageInfo + +type UpdateGroupMemberNameReq struct { + GroupId int64 `protobuf:"varint,1,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"` + PersonId string `protobuf:"bytes,2,opt,name=person_id,json=personId,proto3" json:"person_id,omitempty"` + MemberName string `protobuf:"bytes,3,opt,name=member_name,json=memberName,proto3" json:"member_name,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *UpdateGroupMemberNameReq) Reset() { *m = UpdateGroupMemberNameReq{} } +func (m *UpdateGroupMemberNameReq) String() string { return proto.CompactTextString(m) } +func (*UpdateGroupMemberNameReq) ProtoMessage() {} +func (*UpdateGroupMemberNameReq) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{36} +} + +func (m *UpdateGroupMemberNameReq) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_UpdateGroupMemberNameReq.Unmarshal(m, b) +} +func (m *UpdateGroupMemberNameReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_UpdateGroupMemberNameReq.Marshal(b, m, deterministic) +} +func (m *UpdateGroupMemberNameReq) XXX_Merge(src proto.Message) { + xxx_messageInfo_UpdateGroupMemberNameReq.Merge(m, src) +} +func (m *UpdateGroupMemberNameReq) XXX_Size() int { + return xxx_messageInfo_UpdateGroupMemberNameReq.Size(m) +} +func (m *UpdateGroupMemberNameReq) XXX_DiscardUnknown() { + xxx_messageInfo_UpdateGroupMemberNameReq.DiscardUnknown(m) +} + +var xxx_messageInfo_UpdateGroupMemberNameReq proto.InternalMessageInfo + +func (m *UpdateGroupMemberNameReq) GetGroupId() int64 { + if m != nil { + return m.GroupId + } + return 0 +} + +func (m *UpdateGroupMemberNameReq) GetPersonId() string { + if m != nil { + return m.PersonId + } + return "" +} + +func (m *UpdateGroupMemberNameReq) GetMemberName() string { + if m != nil { + return m.MemberName + } + return "" +} + +type UpdateGroupMemberNameResp struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *UpdateGroupMemberNameResp) Reset() { *m = UpdateGroupMemberNameResp{} } +func (m *UpdateGroupMemberNameResp) String() string { return proto.CompactTextString(m) } +func (*UpdateGroupMemberNameResp) ProtoMessage() {} +func (*UpdateGroupMemberNameResp) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{37} +} + +func (m *UpdateGroupMemberNameResp) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_UpdateGroupMemberNameResp.Unmarshal(m, b) +} +func (m *UpdateGroupMemberNameResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_UpdateGroupMemberNameResp.Marshal(b, m, deterministic) +} +func (m *UpdateGroupMemberNameResp) XXX_Merge(src proto.Message) { + xxx_messageInfo_UpdateGroupMemberNameResp.Merge(m, src) +} +func (m *UpdateGroupMemberNameResp) XXX_Size() int { + return xxx_messageInfo_UpdateGroupMemberNameResp.Size(m) +} +func (m *UpdateGroupMemberNameResp) XXX_DiscardUnknown() { + xxx_messageInfo_UpdateGroupMemberNameResp.DiscardUnknown(m) +} + +var xxx_messageInfo_UpdateGroupMemberNameResp proto.InternalMessageInfo + +type SetAdminReq struct { + GroupId int64 `protobuf:"varint,1,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"` + PersonId string `protobuf:"bytes,2,opt,name=person_id,json=personId,proto3" json:"person_id,omitempty"` + MemberId string `protobuf:"bytes,3,opt,name=member_id,json=memberId,proto3" json:"member_id,omitempty"` + GroupMemberType GroupMemberType `protobuf:"varint,4,opt,name=group_member_type,json=groupMemberType,proto3,enum=dtalk.group.GroupMemberType" json:"group_member_type,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *SetAdminReq) Reset() { *m = SetAdminReq{} } +func (m *SetAdminReq) String() string { return proto.CompactTextString(m) } +func (*SetAdminReq) ProtoMessage() {} +func (*SetAdminReq) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{38} +} + +func (m *SetAdminReq) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_SetAdminReq.Unmarshal(m, b) +} +func (m *SetAdminReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_SetAdminReq.Marshal(b, m, deterministic) +} +func (m *SetAdminReq) XXX_Merge(src proto.Message) { + xxx_messageInfo_SetAdminReq.Merge(m, src) +} +func (m *SetAdminReq) XXX_Size() int { + return xxx_messageInfo_SetAdminReq.Size(m) +} +func (m *SetAdminReq) XXX_DiscardUnknown() { + xxx_messageInfo_SetAdminReq.DiscardUnknown(m) +} + +var xxx_messageInfo_SetAdminReq proto.InternalMessageInfo + +func (m *SetAdminReq) GetGroupId() int64 { + if m != nil { + return m.GroupId + } + return 0 +} + +func (m *SetAdminReq) GetPersonId() string { + if m != nil { + return m.PersonId + } + return "" +} + +func (m *SetAdminReq) GetMemberId() string { + if m != nil { + return m.MemberId + } + return "" +} + +func (m *SetAdminReq) GetGroupMemberType() GroupMemberType { + if m != nil { + return m.GroupMemberType + } + return GroupMemberType_GROUP_MEMBER_TYPE_NORMAL +} + +type SetAdminResp struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *SetAdminResp) Reset() { *m = SetAdminResp{} } +func (m *SetAdminResp) String() string { return proto.CompactTextString(m) } +func (*SetAdminResp) ProtoMessage() {} +func (*SetAdminResp) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{39} +} + +func (m *SetAdminResp) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_SetAdminResp.Unmarshal(m, b) +} +func (m *SetAdminResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_SetAdminResp.Marshal(b, m, deterministic) +} +func (m *SetAdminResp) XXX_Merge(src proto.Message) { + xxx_messageInfo_SetAdminResp.Merge(m, src) +} +func (m *SetAdminResp) XXX_Size() int { + return xxx_messageInfo_SetAdminResp.Size(m) +} +func (m *SetAdminResp) XXX_DiscardUnknown() { + xxx_messageInfo_SetAdminResp.DiscardUnknown(m) +} + +var xxx_messageInfo_SetAdminResp proto.InternalMessageInfo + +type UpdateGroupMemberMuteTimeReq struct { + GroupId int64 `protobuf:"varint,1,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"` + PersonId string `protobuf:"bytes,2,opt,name=person_id,json=personId,proto3" json:"person_id,omitempty"` + MemberIds []string `protobuf:"bytes,3,rep,name=member_ids,json=memberIds,proto3" json:"member_ids,omitempty"` + MuteTime int64 `protobuf:"varint,4,opt,name=mute_time,json=muteTime,proto3" json:"mute_time,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *UpdateGroupMemberMuteTimeReq) Reset() { *m = UpdateGroupMemberMuteTimeReq{} } +func (m *UpdateGroupMemberMuteTimeReq) String() string { return proto.CompactTextString(m) } +func (*UpdateGroupMemberMuteTimeReq) ProtoMessage() {} +func (*UpdateGroupMemberMuteTimeReq) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{40} +} + +func (m *UpdateGroupMemberMuteTimeReq) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_UpdateGroupMemberMuteTimeReq.Unmarshal(m, b) +} +func (m *UpdateGroupMemberMuteTimeReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_UpdateGroupMemberMuteTimeReq.Marshal(b, m, deterministic) +} +func (m *UpdateGroupMemberMuteTimeReq) XXX_Merge(src proto.Message) { + xxx_messageInfo_UpdateGroupMemberMuteTimeReq.Merge(m, src) +} +func (m *UpdateGroupMemberMuteTimeReq) XXX_Size() int { + return xxx_messageInfo_UpdateGroupMemberMuteTimeReq.Size(m) +} +func (m *UpdateGroupMemberMuteTimeReq) XXX_DiscardUnknown() { + xxx_messageInfo_UpdateGroupMemberMuteTimeReq.DiscardUnknown(m) +} + +var xxx_messageInfo_UpdateGroupMemberMuteTimeReq proto.InternalMessageInfo + +func (m *UpdateGroupMemberMuteTimeReq) GetGroupId() int64 { + if m != nil { + return m.GroupId + } + return 0 +} + +func (m *UpdateGroupMemberMuteTimeReq) GetPersonId() string { + if m != nil { + return m.PersonId + } + return "" +} + +func (m *UpdateGroupMemberMuteTimeReq) GetMemberIds() []string { + if m != nil { + return m.MemberIds + } + return nil +} + +func (m *UpdateGroupMemberMuteTimeReq) GetMuteTime() int64 { + if m != nil { + return m.MuteTime + } + return 0 +} + +type UpdateGroupMemberMuteTimeResp struct { + Members []*GroupMemberBizInfo `protobuf:"bytes,1,rep,name=members,proto3" json:"members,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *UpdateGroupMemberMuteTimeResp) Reset() { *m = UpdateGroupMemberMuteTimeResp{} } +func (m *UpdateGroupMemberMuteTimeResp) String() string { return proto.CompactTextString(m) } +func (*UpdateGroupMemberMuteTimeResp) ProtoMessage() {} +func (*UpdateGroupMemberMuteTimeResp) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{41} +} + +func (m *UpdateGroupMemberMuteTimeResp) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_UpdateGroupMemberMuteTimeResp.Unmarshal(m, b) +} +func (m *UpdateGroupMemberMuteTimeResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_UpdateGroupMemberMuteTimeResp.Marshal(b, m, deterministic) +} +func (m *UpdateGroupMemberMuteTimeResp) XXX_Merge(src proto.Message) { + xxx_messageInfo_UpdateGroupMemberMuteTimeResp.Merge(m, src) +} +func (m *UpdateGroupMemberMuteTimeResp) XXX_Size() int { + return xxx_messageInfo_UpdateGroupMemberMuteTimeResp.Size(m) +} +func (m *UpdateGroupMemberMuteTimeResp) XXX_DiscardUnknown() { + xxx_messageInfo_UpdateGroupMemberMuteTimeResp.DiscardUnknown(m) +} + +var xxx_messageInfo_UpdateGroupMemberMuteTimeResp proto.InternalMessageInfo + +func (m *UpdateGroupMemberMuteTimeResp) GetMembers() []*GroupMemberBizInfo { + if m != nil { + return m.Members + } + return nil +} + +type GetPriGroupInfoReq struct { + GroupId int64 `protobuf:"varint,1,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"` + PersonId string `protobuf:"bytes,2,opt,name=person_id,json=personId,proto3" json:"person_id,omitempty"` + DisplayNum int32 `protobuf:"varint,3,opt,name=display_num,json=displayNum,proto3" json:"display_num,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetPriGroupInfoReq) Reset() { *m = GetPriGroupInfoReq{} } +func (m *GetPriGroupInfoReq) String() string { return proto.CompactTextString(m) } +func (*GetPriGroupInfoReq) ProtoMessage() {} +func (*GetPriGroupInfoReq) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{42} +} + +func (m *GetPriGroupInfoReq) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetPriGroupInfoReq.Unmarshal(m, b) +} +func (m *GetPriGroupInfoReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetPriGroupInfoReq.Marshal(b, m, deterministic) +} +func (m *GetPriGroupInfoReq) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetPriGroupInfoReq.Merge(m, src) +} +func (m *GetPriGroupInfoReq) XXX_Size() int { + return xxx_messageInfo_GetPriGroupInfoReq.Size(m) +} +func (m *GetPriGroupInfoReq) XXX_DiscardUnknown() { + xxx_messageInfo_GetPriGroupInfoReq.DiscardUnknown(m) +} + +var xxx_messageInfo_GetPriGroupInfoReq proto.InternalMessageInfo + +func (m *GetPriGroupInfoReq) GetGroupId() int64 { + if m != nil { + return m.GroupId + } + return 0 +} + +func (m *GetPriGroupInfoReq) GetPersonId() string { + if m != nil { + return m.PersonId + } + return "" +} + +func (m *GetPriGroupInfoReq) GetDisplayNum() int32 { + if m != nil { + return m.DisplayNum + } + return 0 +} + +type GetPriGroupInfoResp struct { + Group *GroupBizInfo `protobuf:"bytes,1,opt,name=group,proto3" json:"group,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetPriGroupInfoResp) Reset() { *m = GetPriGroupInfoResp{} } +func (m *GetPriGroupInfoResp) String() string { return proto.CompactTextString(m) } +func (*GetPriGroupInfoResp) ProtoMessage() {} +func (*GetPriGroupInfoResp) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{43} +} + +func (m *GetPriGroupInfoResp) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetPriGroupInfoResp.Unmarshal(m, b) +} +func (m *GetPriGroupInfoResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetPriGroupInfoResp.Marshal(b, m, deterministic) +} +func (m *GetPriGroupInfoResp) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetPriGroupInfoResp.Merge(m, src) +} +func (m *GetPriGroupInfoResp) XXX_Size() int { + return xxx_messageInfo_GetPriGroupInfoResp.Size(m) +} +func (m *GetPriGroupInfoResp) XXX_DiscardUnknown() { + xxx_messageInfo_GetPriGroupInfoResp.DiscardUnknown(m) +} + +var xxx_messageInfo_GetPriGroupInfoResp proto.InternalMessageInfo + +func (m *GetPriGroupInfoResp) GetGroup() *GroupBizInfo { + if m != nil { + return m.Group + } + return nil +} + +type GetPubGroupInfoReq struct { + GroupId int64 `protobuf:"varint,1,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"` + PersonId string `protobuf:"bytes,2,opt,name=person_id,json=personId,proto3" json:"person_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetPubGroupInfoReq) Reset() { *m = GetPubGroupInfoReq{} } +func (m *GetPubGroupInfoReq) String() string { return proto.CompactTextString(m) } +func (*GetPubGroupInfoReq) ProtoMessage() {} +func (*GetPubGroupInfoReq) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{44} +} + +func (m *GetPubGroupInfoReq) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetPubGroupInfoReq.Unmarshal(m, b) +} +func (m *GetPubGroupInfoReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetPubGroupInfoReq.Marshal(b, m, deterministic) +} +func (m *GetPubGroupInfoReq) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetPubGroupInfoReq.Merge(m, src) +} +func (m *GetPubGroupInfoReq) XXX_Size() int { + return xxx_messageInfo_GetPubGroupInfoReq.Size(m) +} +func (m *GetPubGroupInfoReq) XXX_DiscardUnknown() { + xxx_messageInfo_GetPubGroupInfoReq.DiscardUnknown(m) +} + +var xxx_messageInfo_GetPubGroupInfoReq proto.InternalMessageInfo + +func (m *GetPubGroupInfoReq) GetGroupId() int64 { + if m != nil { + return m.GroupId + } + return 0 +} + +func (m *GetPubGroupInfoReq) GetPersonId() string { + if m != nil { + return m.PersonId + } + return "" +} + +type GetPubGroupInfoResp struct { + Group *GroupBizInfo `protobuf:"bytes,1,opt,name=group,proto3" json:"group,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetPubGroupInfoResp) Reset() { *m = GetPubGroupInfoResp{} } +func (m *GetPubGroupInfoResp) String() string { return proto.CompactTextString(m) } +func (*GetPubGroupInfoResp) ProtoMessage() {} +func (*GetPubGroupInfoResp) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{45} +} + +func (m *GetPubGroupInfoResp) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetPubGroupInfoResp.Unmarshal(m, b) +} +func (m *GetPubGroupInfoResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetPubGroupInfoResp.Marshal(b, m, deterministic) +} +func (m *GetPubGroupInfoResp) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetPubGroupInfoResp.Merge(m, src) +} +func (m *GetPubGroupInfoResp) XXX_Size() int { + return xxx_messageInfo_GetPubGroupInfoResp.Size(m) +} +func (m *GetPubGroupInfoResp) XXX_DiscardUnknown() { + xxx_messageInfo_GetPubGroupInfoResp.DiscardUnknown(m) +} + +var xxx_messageInfo_GetPubGroupInfoResp proto.InternalMessageInfo + +func (m *GetPubGroupInfoResp) GetGroup() *GroupBizInfo { + if m != nil { + return m.Group + } + return nil +} + +type GetGroupListReq struct { + PersonId string `protobuf:"bytes,1,opt,name=person_id,json=personId,proto3" json:"person_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetGroupListReq) Reset() { *m = GetGroupListReq{} } +func (m *GetGroupListReq) String() string { return proto.CompactTextString(m) } +func (*GetGroupListReq) ProtoMessage() {} +func (*GetGroupListReq) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{46} +} + +func (m *GetGroupListReq) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetGroupListReq.Unmarshal(m, b) +} +func (m *GetGroupListReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetGroupListReq.Marshal(b, m, deterministic) +} +func (m *GetGroupListReq) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetGroupListReq.Merge(m, src) +} +func (m *GetGroupListReq) XXX_Size() int { + return xxx_messageInfo_GetGroupListReq.Size(m) +} +func (m *GetGroupListReq) XXX_DiscardUnknown() { + xxx_messageInfo_GetGroupListReq.DiscardUnknown(m) +} + +var xxx_messageInfo_GetGroupListReq proto.InternalMessageInfo + +func (m *GetGroupListReq) GetPersonId() string { + if m != nil { + return m.PersonId + } + return "" +} + +type GetGroupListResp struct { + Groups []*GroupBizInfo `protobuf:"bytes,1,rep,name=groups,proto3" json:"groups,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetGroupListResp) Reset() { *m = GetGroupListResp{} } +func (m *GetGroupListResp) String() string { return proto.CompactTextString(m) } +func (*GetGroupListResp) ProtoMessage() {} +func (*GetGroupListResp) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{47} +} + +func (m *GetGroupListResp) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetGroupListResp.Unmarshal(m, b) +} +func (m *GetGroupListResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetGroupListResp.Marshal(b, m, deterministic) +} +func (m *GetGroupListResp) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetGroupListResp.Merge(m, src) +} +func (m *GetGroupListResp) XXX_Size() int { + return xxx_messageInfo_GetGroupListResp.Size(m) +} +func (m *GetGroupListResp) XXX_DiscardUnknown() { + xxx_messageInfo_GetGroupListResp.DiscardUnknown(m) +} + +var xxx_messageInfo_GetGroupListResp proto.InternalMessageInfo + +func (m *GetGroupListResp) GetGroups() []*GroupBizInfo { + if m != nil { + return m.Groups + } + return nil +} + +type GetGroupMemberListReq struct { + GroupId int64 `protobuf:"varint,1,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"` + PersonId string `protobuf:"bytes,2,opt,name=person_id,json=personId,proto3" json:"person_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetGroupMemberListReq) Reset() { *m = GetGroupMemberListReq{} } +func (m *GetGroupMemberListReq) String() string { return proto.CompactTextString(m) } +func (*GetGroupMemberListReq) ProtoMessage() {} +func (*GetGroupMemberListReq) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{48} +} + +func (m *GetGroupMemberListReq) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetGroupMemberListReq.Unmarshal(m, b) +} +func (m *GetGroupMemberListReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetGroupMemberListReq.Marshal(b, m, deterministic) +} +func (m *GetGroupMemberListReq) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetGroupMemberListReq.Merge(m, src) +} +func (m *GetGroupMemberListReq) XXX_Size() int { + return xxx_messageInfo_GetGroupMemberListReq.Size(m) +} +func (m *GetGroupMemberListReq) XXX_DiscardUnknown() { + xxx_messageInfo_GetGroupMemberListReq.DiscardUnknown(m) +} + +var xxx_messageInfo_GetGroupMemberListReq proto.InternalMessageInfo + +func (m *GetGroupMemberListReq) GetGroupId() int64 { + if m != nil { + return m.GroupId + } + return 0 +} + +func (m *GetGroupMemberListReq) GetPersonId() string { + if m != nil { + return m.PersonId + } + return "" +} + +type GetGroupMemberListResp struct { + Members []*GroupMemberBizInfo `protobuf:"bytes,1,rep,name=members,proto3" json:"members,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetGroupMemberListResp) Reset() { *m = GetGroupMemberListResp{} } +func (m *GetGroupMemberListResp) String() string { return proto.CompactTextString(m) } +func (*GetGroupMemberListResp) ProtoMessage() {} +func (*GetGroupMemberListResp) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{49} +} + +func (m *GetGroupMemberListResp) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetGroupMemberListResp.Unmarshal(m, b) +} +func (m *GetGroupMemberListResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetGroupMemberListResp.Marshal(b, m, deterministic) +} +func (m *GetGroupMemberListResp) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetGroupMemberListResp.Merge(m, src) +} +func (m *GetGroupMemberListResp) XXX_Size() int { + return xxx_messageInfo_GetGroupMemberListResp.Size(m) +} +func (m *GetGroupMemberListResp) XXX_DiscardUnknown() { + xxx_messageInfo_GetGroupMemberListResp.DiscardUnknown(m) +} + +var xxx_messageInfo_GetGroupMemberListResp proto.InternalMessageInfo + +func (m *GetGroupMemberListResp) GetMembers() []*GroupMemberBizInfo { + if m != nil { + return m.Members + } + return nil +} + +type GetGroupMemberInfoReq struct { + GroupId int64 `protobuf:"varint,1,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"` + PersonId string `protobuf:"bytes,2,opt,name=person_id,json=personId,proto3" json:"person_id,omitempty"` + MemberId string `protobuf:"bytes,3,opt,name=member_id,json=memberId,proto3" json:"member_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetGroupMemberInfoReq) Reset() { *m = GetGroupMemberInfoReq{} } +func (m *GetGroupMemberInfoReq) String() string { return proto.CompactTextString(m) } +func (*GetGroupMemberInfoReq) ProtoMessage() {} +func (*GetGroupMemberInfoReq) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{50} +} + +func (m *GetGroupMemberInfoReq) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetGroupMemberInfoReq.Unmarshal(m, b) +} +func (m *GetGroupMemberInfoReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetGroupMemberInfoReq.Marshal(b, m, deterministic) +} +func (m *GetGroupMemberInfoReq) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetGroupMemberInfoReq.Merge(m, src) +} +func (m *GetGroupMemberInfoReq) XXX_Size() int { + return xxx_messageInfo_GetGroupMemberInfoReq.Size(m) +} +func (m *GetGroupMemberInfoReq) XXX_DiscardUnknown() { + xxx_messageInfo_GetGroupMemberInfoReq.DiscardUnknown(m) +} + +var xxx_messageInfo_GetGroupMemberInfoReq proto.InternalMessageInfo + +func (m *GetGroupMemberInfoReq) GetGroupId() int64 { + if m != nil { + return m.GroupId + } + return 0 +} + +func (m *GetGroupMemberInfoReq) GetPersonId() string { + if m != nil { + return m.PersonId + } + return "" +} + +func (m *GetGroupMemberInfoReq) GetMemberId() string { + if m != nil { + return m.MemberId + } + return "" +} + +type GetGroupMemberInfoResp struct { + Member *GroupMemberBizInfo `protobuf:"bytes,1,opt,name=member,proto3" json:"member,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetGroupMemberInfoResp) Reset() { *m = GetGroupMemberInfoResp{} } +func (m *GetGroupMemberInfoResp) String() string { return proto.CompactTextString(m) } +func (*GetGroupMemberInfoResp) ProtoMessage() {} +func (*GetGroupMemberInfoResp) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{51} +} + +func (m *GetGroupMemberInfoResp) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetGroupMemberInfoResp.Unmarshal(m, b) +} +func (m *GetGroupMemberInfoResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetGroupMemberInfoResp.Marshal(b, m, deterministic) +} +func (m *GetGroupMemberInfoResp) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetGroupMemberInfoResp.Merge(m, src) +} +func (m *GetGroupMemberInfoResp) XXX_Size() int { + return xxx_messageInfo_GetGroupMemberInfoResp.Size(m) +} +func (m *GetGroupMemberInfoResp) XXX_DiscardUnknown() { + xxx_messageInfo_GetGroupMemberInfoResp.DiscardUnknown(m) +} + +var xxx_messageInfo_GetGroupMemberInfoResp proto.InternalMessageInfo + +func (m *GetGroupMemberInfoResp) GetMember() *GroupMemberBizInfo { + if m != nil { + return m.Member + } + return nil +} + +type GetMuteListReq struct { + GroupId int64 `protobuf:"varint,1,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"` + PersonId string `protobuf:"bytes,2,opt,name=person_id,json=personId,proto3" json:"person_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetMuteListReq) Reset() { *m = GetMuteListReq{} } +func (m *GetMuteListReq) String() string { return proto.CompactTextString(m) } +func (*GetMuteListReq) ProtoMessage() {} +func (*GetMuteListReq) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{52} +} + +func (m *GetMuteListReq) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetMuteListReq.Unmarshal(m, b) +} +func (m *GetMuteListReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetMuteListReq.Marshal(b, m, deterministic) +} +func (m *GetMuteListReq) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetMuteListReq.Merge(m, src) +} +func (m *GetMuteListReq) XXX_Size() int { + return xxx_messageInfo_GetMuteListReq.Size(m) +} +func (m *GetMuteListReq) XXX_DiscardUnknown() { + xxx_messageInfo_GetMuteListReq.DiscardUnknown(m) +} + +var xxx_messageInfo_GetMuteListReq proto.InternalMessageInfo + +func (m *GetMuteListReq) GetGroupId() int64 { + if m != nil { + return m.GroupId + } + return 0 +} + +func (m *GetMuteListReq) GetPersonId() string { + if m != nil { + return m.PersonId + } + return "" +} + +type GetMuteListResp struct { + Members []*GroupMemberBizInfo `protobuf:"bytes,1,rep,name=members,proto3" json:"members,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetMuteListResp) Reset() { *m = GetMuteListResp{} } +func (m *GetMuteListResp) String() string { return proto.CompactTextString(m) } +func (*GetMuteListResp) ProtoMessage() {} +func (*GetMuteListResp) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{53} +} + +func (m *GetMuteListResp) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetMuteListResp.Unmarshal(m, b) +} +func (m *GetMuteListResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetMuteListResp.Marshal(b, m, deterministic) +} +func (m *GetMuteListResp) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetMuteListResp.Merge(m, src) +} +func (m *GetMuteListResp) XXX_Size() int { + return xxx_messageInfo_GetMuteListResp.Size(m) +} +func (m *GetMuteListResp) XXX_DiscardUnknown() { + xxx_messageInfo_GetMuteListResp.DiscardUnknown(m) +} + +var xxx_messageInfo_GetMuteListResp proto.InternalMessageInfo + +func (m *GetMuteListResp) GetMembers() []*GroupMemberBizInfo { + if m != nil { + return m.Members + } + return nil +} + +type ForceAddMemberReq struct { + MemberId string `protobuf:"bytes,1,opt,name=member_id,json=memberId,proto3" json:"member_id,omitempty"` + GroupId int64 `protobuf:"varint,2,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ForceAddMemberReq) Reset() { *m = ForceAddMemberReq{} } +func (m *ForceAddMemberReq) String() string { return proto.CompactTextString(m) } +func (*ForceAddMemberReq) ProtoMessage() {} +func (*ForceAddMemberReq) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{54} +} + +func (m *ForceAddMemberReq) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ForceAddMemberReq.Unmarshal(m, b) +} +func (m *ForceAddMemberReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ForceAddMemberReq.Marshal(b, m, deterministic) +} +func (m *ForceAddMemberReq) XXX_Merge(src proto.Message) { + xxx_messageInfo_ForceAddMemberReq.Merge(m, src) +} +func (m *ForceAddMemberReq) XXX_Size() int { + return xxx_messageInfo_ForceAddMemberReq.Size(m) +} +func (m *ForceAddMemberReq) XXX_DiscardUnknown() { + xxx_messageInfo_ForceAddMemberReq.DiscardUnknown(m) +} + +var xxx_messageInfo_ForceAddMemberReq proto.InternalMessageInfo + +func (m *ForceAddMemberReq) GetMemberId() string { + if m != nil { + return m.MemberId + } + return "" +} + +func (m *ForceAddMemberReq) GetGroupId() int64 { + if m != nil { + return m.GroupId + } + return 0 +} + +type ForceAddMemberResp struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ForceAddMemberResp) Reset() { *m = ForceAddMemberResp{} } +func (m *ForceAddMemberResp) String() string { return proto.CompactTextString(m) } +func (*ForceAddMemberResp) ProtoMessage() {} +func (*ForceAddMemberResp) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{55} +} + +func (m *ForceAddMemberResp) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ForceAddMemberResp.Unmarshal(m, b) +} +func (m *ForceAddMemberResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ForceAddMemberResp.Marshal(b, m, deterministic) +} +func (m *ForceAddMemberResp) XXX_Merge(src proto.Message) { + xxx_messageInfo_ForceAddMemberResp.Merge(m, src) +} +func (m *ForceAddMemberResp) XXX_Size() int { + return xxx_messageInfo_ForceAddMemberResp.Size(m) +} +func (m *ForceAddMemberResp) XXX_DiscardUnknown() { + xxx_messageInfo_ForceAddMemberResp.DiscardUnknown(m) +} + +var xxx_messageInfo_ForceAddMemberResp proto.InternalMessageInfo + +type ForceAddMembersReq struct { + GroupId int64 `protobuf:"varint,1,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"` + Members []*GroupMemberInfo `protobuf:"bytes,2,rep,name=members,proto3" json:"members,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ForceAddMembersReq) Reset() { *m = ForceAddMembersReq{} } +func (m *ForceAddMembersReq) String() string { return proto.CompactTextString(m) } +func (*ForceAddMembersReq) ProtoMessage() {} +func (*ForceAddMembersReq) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{56} +} + +func (m *ForceAddMembersReq) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ForceAddMembersReq.Unmarshal(m, b) +} +func (m *ForceAddMembersReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ForceAddMembersReq.Marshal(b, m, deterministic) +} +func (m *ForceAddMembersReq) XXX_Merge(src proto.Message) { + xxx_messageInfo_ForceAddMembersReq.Merge(m, src) +} +func (m *ForceAddMembersReq) XXX_Size() int { + return xxx_messageInfo_ForceAddMembersReq.Size(m) +} +func (m *ForceAddMembersReq) XXX_DiscardUnknown() { + xxx_messageInfo_ForceAddMembersReq.DiscardUnknown(m) +} + +var xxx_messageInfo_ForceAddMembersReq proto.InternalMessageInfo + +func (m *ForceAddMembersReq) GetGroupId() int64 { + if m != nil { + return m.GroupId + } + return 0 +} + +func (m *ForceAddMembersReq) GetMembers() []*GroupMemberInfo { + if m != nil { + return m.Members + } + return nil +} + +type ForceAddMembersResp struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ForceAddMembersResp) Reset() { *m = ForceAddMembersResp{} } +func (m *ForceAddMembersResp) String() string { return proto.CompactTextString(m) } +func (*ForceAddMembersResp) ProtoMessage() {} +func (*ForceAddMembersResp) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{57} +} + +func (m *ForceAddMembersResp) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ForceAddMembersResp.Unmarshal(m, b) +} +func (m *ForceAddMembersResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ForceAddMembersResp.Marshal(b, m, deterministic) +} +func (m *ForceAddMembersResp) XXX_Merge(src proto.Message) { + xxx_messageInfo_ForceAddMembersResp.Merge(m, src) +} +func (m *ForceAddMembersResp) XXX_Size() int { + return xxx_messageInfo_ForceAddMembersResp.Size(m) +} +func (m *ForceAddMembersResp) XXX_DiscardUnknown() { + xxx_messageInfo_ForceAddMembersResp.DiscardUnknown(m) +} + +var xxx_messageInfo_ForceAddMembersResp proto.InternalMessageInfo + +type ForceDeleteMemberReq struct { + MemberId string `protobuf:"bytes,1,opt,name=member_id,json=memberId,proto3" json:"member_id,omitempty"` + GroupId int64 `protobuf:"varint,2,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ForceDeleteMemberReq) Reset() { *m = ForceDeleteMemberReq{} } +func (m *ForceDeleteMemberReq) String() string { return proto.CompactTextString(m) } +func (*ForceDeleteMemberReq) ProtoMessage() {} +func (*ForceDeleteMemberReq) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{58} +} + +func (m *ForceDeleteMemberReq) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ForceDeleteMemberReq.Unmarshal(m, b) +} +func (m *ForceDeleteMemberReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ForceDeleteMemberReq.Marshal(b, m, deterministic) +} +func (m *ForceDeleteMemberReq) XXX_Merge(src proto.Message) { + xxx_messageInfo_ForceDeleteMemberReq.Merge(m, src) +} +func (m *ForceDeleteMemberReq) XXX_Size() int { + return xxx_messageInfo_ForceDeleteMemberReq.Size(m) +} +func (m *ForceDeleteMemberReq) XXX_DiscardUnknown() { + xxx_messageInfo_ForceDeleteMemberReq.DiscardUnknown(m) +} + +var xxx_messageInfo_ForceDeleteMemberReq proto.InternalMessageInfo + +func (m *ForceDeleteMemberReq) GetMemberId() string { + if m != nil { + return m.MemberId + } + return "" +} + +func (m *ForceDeleteMemberReq) GetGroupId() int64 { + if m != nil { + return m.GroupId + } + return 0 +} + +type ForceDeleteMemberResp struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ForceDeleteMemberResp) Reset() { *m = ForceDeleteMemberResp{} } +func (m *ForceDeleteMemberResp) String() string { return proto.CompactTextString(m) } +func (*ForceDeleteMemberResp) ProtoMessage() {} +func (*ForceDeleteMemberResp) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{59} +} + +func (m *ForceDeleteMemberResp) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ForceDeleteMemberResp.Unmarshal(m, b) +} +func (m *ForceDeleteMemberResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ForceDeleteMemberResp.Marshal(b, m, deterministic) +} +func (m *ForceDeleteMemberResp) XXX_Merge(src proto.Message) { + xxx_messageInfo_ForceDeleteMemberResp.Merge(m, src) +} +func (m *ForceDeleteMemberResp) XXX_Size() int { + return xxx_messageInfo_ForceDeleteMemberResp.Size(m) +} +func (m *ForceDeleteMemberResp) XXX_DiscardUnknown() { + xxx_messageInfo_ForceDeleteMemberResp.DiscardUnknown(m) +} + +var xxx_messageInfo_ForceDeleteMemberResp proto.InternalMessageInfo + +type ForceDeleteMembersReq struct { + GroupId int64 `protobuf:"varint,1,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"` + MemberIds []string `protobuf:"bytes,2,rep,name=member_ids,json=memberIds,proto3" json:"member_ids,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ForceDeleteMembersReq) Reset() { *m = ForceDeleteMembersReq{} } +func (m *ForceDeleteMembersReq) String() string { return proto.CompactTextString(m) } +func (*ForceDeleteMembersReq) ProtoMessage() {} +func (*ForceDeleteMembersReq) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{60} +} + +func (m *ForceDeleteMembersReq) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ForceDeleteMembersReq.Unmarshal(m, b) +} +func (m *ForceDeleteMembersReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ForceDeleteMembersReq.Marshal(b, m, deterministic) +} +func (m *ForceDeleteMembersReq) XXX_Merge(src proto.Message) { + xxx_messageInfo_ForceDeleteMembersReq.Merge(m, src) +} +func (m *ForceDeleteMembersReq) XXX_Size() int { + return xxx_messageInfo_ForceDeleteMembersReq.Size(m) +} +func (m *ForceDeleteMembersReq) XXX_DiscardUnknown() { + xxx_messageInfo_ForceDeleteMembersReq.DiscardUnknown(m) +} + +var xxx_messageInfo_ForceDeleteMembersReq proto.InternalMessageInfo + +func (m *ForceDeleteMembersReq) GetGroupId() int64 { + if m != nil { + return m.GroupId + } + return 0 +} + +func (m *ForceDeleteMembersReq) GetMemberIds() []string { + if m != nil { + return m.MemberIds + } + return nil +} + +type ForceDeleteMembersResp struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ForceDeleteMembersResp) Reset() { *m = ForceDeleteMembersResp{} } +func (m *ForceDeleteMembersResp) String() string { return proto.CompactTextString(m) } +func (*ForceDeleteMembersResp) ProtoMessage() {} +func (*ForceDeleteMembersResp) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{61} +} + +func (m *ForceDeleteMembersResp) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ForceDeleteMembersResp.Unmarshal(m, b) +} +func (m *ForceDeleteMembersResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ForceDeleteMembersResp.Marshal(b, m, deterministic) +} +func (m *ForceDeleteMembersResp) XXX_Merge(src proto.Message) { + xxx_messageInfo_ForceDeleteMembersResp.Merge(m, src) +} +func (m *ForceDeleteMembersResp) XXX_Size() int { + return xxx_messageInfo_ForceDeleteMembersResp.Size(m) +} +func (m *ForceDeleteMembersResp) XXX_DiscardUnknown() { + xxx_messageInfo_ForceDeleteMembersResp.DiscardUnknown(m) +} + +var xxx_messageInfo_ForceDeleteMembersResp proto.InternalMessageInfo + +type GetMemberReq struct { + MemberId string `protobuf:"bytes,1,opt,name=member_id,json=memberId,proto3" json:"member_id,omitempty"` + GroupId int64 `protobuf:"varint,2,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetMemberReq) Reset() { *m = GetMemberReq{} } +func (m *GetMemberReq) String() string { return proto.CompactTextString(m) } +func (*GetMemberReq) ProtoMessage() {} +func (*GetMemberReq) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{62} +} + +func (m *GetMemberReq) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetMemberReq.Unmarshal(m, b) +} +func (m *GetMemberReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetMemberReq.Marshal(b, m, deterministic) +} +func (m *GetMemberReq) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetMemberReq.Merge(m, src) +} +func (m *GetMemberReq) XXX_Size() int { + return xxx_messageInfo_GetMemberReq.Size(m) +} +func (m *GetMemberReq) XXX_DiscardUnknown() { + xxx_messageInfo_GetMemberReq.DiscardUnknown(m) +} + +var xxx_messageInfo_GetMemberReq proto.InternalMessageInfo + +func (m *GetMemberReq) GetMemberId() string { + if m != nil { + return m.MemberId + } + return "" +} + +func (m *GetMemberReq) GetGroupId() int64 { + if m != nil { + return m.GroupId + } + return 0 +} + +type GetMemberResp struct { + GroupId int64 `protobuf:"varint,1,opt,name=groupId,proto3" json:"groupId,omitempty"` + GroupMemberId string `protobuf:"bytes,2,opt,name=groupMemberId,proto3" json:"groupMemberId,omitempty"` + GroupMemberName string `protobuf:"bytes,3,opt,name=groupMemberName,proto3" json:"groupMemberName,omitempty"` + GroupMemberType int32 `protobuf:"varint,4,opt,name=groupMemberType,proto3" json:"groupMemberType,omitempty"` + GroupMemberJoinTime int64 `protobuf:"varint,5,opt,name=groupMemberJoinTime,proto3" json:"groupMemberJoinTime,omitempty"` + GroupMemberUpdateTime int64 `protobuf:"varint,6,opt,name=groupMemberUpdateTime,proto3" json:"groupMemberUpdateTime,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetMemberResp) Reset() { *m = GetMemberResp{} } +func (m *GetMemberResp) String() string { return proto.CompactTextString(m) } +func (*GetMemberResp) ProtoMessage() {} +func (*GetMemberResp) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{63} +} + +func (m *GetMemberResp) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetMemberResp.Unmarshal(m, b) +} +func (m *GetMemberResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetMemberResp.Marshal(b, m, deterministic) +} +func (m *GetMemberResp) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetMemberResp.Merge(m, src) +} +func (m *GetMemberResp) XXX_Size() int { + return xxx_messageInfo_GetMemberResp.Size(m) +} +func (m *GetMemberResp) XXX_DiscardUnknown() { + xxx_messageInfo_GetMemberResp.DiscardUnknown(m) +} + +var xxx_messageInfo_GetMemberResp proto.InternalMessageInfo + +func (m *GetMemberResp) GetGroupId() int64 { + if m != nil { + return m.GroupId + } + return 0 +} + +func (m *GetMemberResp) GetGroupMemberId() string { + if m != nil { + return m.GroupMemberId + } + return "" +} + +func (m *GetMemberResp) GetGroupMemberName() string { + if m != nil { + return m.GroupMemberName + } + return "" +} + +func (m *GetMemberResp) GetGroupMemberType() int32 { + if m != nil { + return m.GroupMemberType + } + return 0 +} + +func (m *GetMemberResp) GetGroupMemberJoinTime() int64 { + if m != nil { + return m.GroupMemberJoinTime + } + return 0 +} + +func (m *GetMemberResp) GetGroupMemberUpdateTime() int64 { + if m != nil { + return m.GroupMemberUpdateTime + } + return 0 +} + +type GetGroupInfoReq struct { + GroupId int64 `protobuf:"varint,1,opt,name=groupId,proto3" json:"groupId,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetGroupInfoReq) Reset() { *m = GetGroupInfoReq{} } +func (m *GetGroupInfoReq) String() string { return proto.CompactTextString(m) } +func (*GetGroupInfoReq) ProtoMessage() {} +func (*GetGroupInfoReq) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{64} +} + +func (m *GetGroupInfoReq) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetGroupInfoReq.Unmarshal(m, b) +} +func (m *GetGroupInfoReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetGroupInfoReq.Marshal(b, m, deterministic) +} +func (m *GetGroupInfoReq) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetGroupInfoReq.Merge(m, src) +} +func (m *GetGroupInfoReq) XXX_Size() int { + return xxx_messageInfo_GetGroupInfoReq.Size(m) +} +func (m *GetGroupInfoReq) XXX_DiscardUnknown() { + xxx_messageInfo_GetGroupInfoReq.DiscardUnknown(m) +} + +var xxx_messageInfo_GetGroupInfoReq proto.InternalMessageInfo + +func (m *GetGroupInfoReq) GetGroupId() int64 { + if m != nil { + return m.GroupId + } + return 0 +} + +type GetGroupInfoResp struct { + GroupId int64 `protobuf:"varint,1,opt,name=groupId,proto3" json:"groupId,omitempty"` + GroupExist bool `protobuf:"varint,2,opt,name=group_exist,json=groupExist,proto3" json:"group_exist,omitempty"` + GroupName string `protobuf:"bytes,3,opt,name=group_name,json=groupName,proto3" json:"group_name,omitempty"` + GroupAvatar string `protobuf:"bytes,4,opt,name=group_avatar,json=groupAvatar,proto3" json:"group_avatar,omitempty"` + GroupOwnerId string `protobuf:"bytes,5,opt,name=group_owner_id,json=groupOwnerId,proto3" json:"group_owner_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetGroupInfoResp) Reset() { *m = GetGroupInfoResp{} } +func (m *GetGroupInfoResp) String() string { return proto.CompactTextString(m) } +func (*GetGroupInfoResp) ProtoMessage() {} +func (*GetGroupInfoResp) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{65} +} + +func (m *GetGroupInfoResp) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetGroupInfoResp.Unmarshal(m, b) +} +func (m *GetGroupInfoResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetGroupInfoResp.Marshal(b, m, deterministic) +} +func (m *GetGroupInfoResp) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetGroupInfoResp.Merge(m, src) +} +func (m *GetGroupInfoResp) XXX_Size() int { + return xxx_messageInfo_GetGroupInfoResp.Size(m) +} +func (m *GetGroupInfoResp) XXX_DiscardUnknown() { + xxx_messageInfo_GetGroupInfoResp.DiscardUnknown(m) +} + +var xxx_messageInfo_GetGroupInfoResp proto.InternalMessageInfo + +func (m *GetGroupInfoResp) GetGroupId() int64 { + if m != nil { + return m.GroupId + } + return 0 +} + +func (m *GetGroupInfoResp) GetGroupExist() bool { + if m != nil { + return m.GroupExist + } + return false +} + +func (m *GetGroupInfoResp) GetGroupName() string { + if m != nil { + return m.GroupName + } + return "" +} + +func (m *GetGroupInfoResp) GetGroupAvatar() string { + if m != nil { + return m.GroupAvatar + } + return "" +} + +func (m *GetGroupInfoResp) GetGroupOwnerId() string { + if m != nil { + return m.GroupOwnerId + } + return "" +} + +type ForceDisbandGroupReq struct { + GroupId int64 `protobuf:"varint,1,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"` + OpeId string `protobuf:"bytes,2,opt,name=ope_id,json=opeId,proto3" json:"ope_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ForceDisbandGroupReq) Reset() { *m = ForceDisbandGroupReq{} } +func (m *ForceDisbandGroupReq) String() string { return proto.CompactTextString(m) } +func (*ForceDisbandGroupReq) ProtoMessage() {} +func (*ForceDisbandGroupReq) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{66} +} + +func (m *ForceDisbandGroupReq) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ForceDisbandGroupReq.Unmarshal(m, b) +} +func (m *ForceDisbandGroupReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ForceDisbandGroupReq.Marshal(b, m, deterministic) +} +func (m *ForceDisbandGroupReq) XXX_Merge(src proto.Message) { + xxx_messageInfo_ForceDisbandGroupReq.Merge(m, src) +} +func (m *ForceDisbandGroupReq) XXX_Size() int { + return xxx_messageInfo_ForceDisbandGroupReq.Size(m) +} +func (m *ForceDisbandGroupReq) XXX_DiscardUnknown() { + xxx_messageInfo_ForceDisbandGroupReq.DiscardUnknown(m) +} + +var xxx_messageInfo_ForceDisbandGroupReq proto.InternalMessageInfo + +func (m *ForceDisbandGroupReq) GetGroupId() int64 { + if m != nil { + return m.GroupId + } + return 0 +} + +func (m *ForceDisbandGroupReq) GetOpeId() string { + if m != nil { + return m.OpeId + } + return "" +} + +type ForceDisbandGroupResp struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ForceDisbandGroupResp) Reset() { *m = ForceDisbandGroupResp{} } +func (m *ForceDisbandGroupResp) String() string { return proto.CompactTextString(m) } +func (*ForceDisbandGroupResp) ProtoMessage() {} +func (*ForceDisbandGroupResp) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{67} +} + +func (m *ForceDisbandGroupResp) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ForceDisbandGroupResp.Unmarshal(m, b) +} +func (m *ForceDisbandGroupResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ForceDisbandGroupResp.Marshal(b, m, deterministic) +} +func (m *ForceDisbandGroupResp) XXX_Merge(src proto.Message) { + xxx_messageInfo_ForceDisbandGroupResp.Merge(m, src) +} +func (m *ForceDisbandGroupResp) XXX_Size() int { + return xxx_messageInfo_ForceDisbandGroupResp.Size(m) +} +func (m *ForceDisbandGroupResp) XXX_DiscardUnknown() { + xxx_messageInfo_ForceDisbandGroupResp.DiscardUnknown(m) +} + +var xxx_messageInfo_ForceDisbandGroupResp proto.InternalMessageInfo + +type ForceUpdateGroupTypeReq struct { + GroupId int64 `protobuf:"varint,1,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"` + GroupType int32 `protobuf:"varint,2,opt,name=group_type,json=groupType,proto3" json:"group_type,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ForceUpdateGroupTypeReq) Reset() { *m = ForceUpdateGroupTypeReq{} } +func (m *ForceUpdateGroupTypeReq) String() string { return proto.CompactTextString(m) } +func (*ForceUpdateGroupTypeReq) ProtoMessage() {} +func (*ForceUpdateGroupTypeReq) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{68} +} + +func (m *ForceUpdateGroupTypeReq) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ForceUpdateGroupTypeReq.Unmarshal(m, b) +} +func (m *ForceUpdateGroupTypeReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ForceUpdateGroupTypeReq.Marshal(b, m, deterministic) +} +func (m *ForceUpdateGroupTypeReq) XXX_Merge(src proto.Message) { + xxx_messageInfo_ForceUpdateGroupTypeReq.Merge(m, src) +} +func (m *ForceUpdateGroupTypeReq) XXX_Size() int { + return xxx_messageInfo_ForceUpdateGroupTypeReq.Size(m) +} +func (m *ForceUpdateGroupTypeReq) XXX_DiscardUnknown() { + xxx_messageInfo_ForceUpdateGroupTypeReq.DiscardUnknown(m) +} + +var xxx_messageInfo_ForceUpdateGroupTypeReq proto.InternalMessageInfo + +func (m *ForceUpdateGroupTypeReq) GetGroupId() int64 { + if m != nil { + return m.GroupId + } + return 0 +} + +func (m *ForceUpdateGroupTypeReq) GetGroupType() int32 { + if m != nil { + return m.GroupType + } + return 0 +} + +type ForceUpdateGroupTypeResp struct { + GroupId int64 `protobuf:"varint,1,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"` + GroupType int32 `protobuf:"varint,2,opt,name=group_type,json=groupType,proto3" json:"group_type,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ForceUpdateGroupTypeResp) Reset() { *m = ForceUpdateGroupTypeResp{} } +func (m *ForceUpdateGroupTypeResp) String() string { return proto.CompactTextString(m) } +func (*ForceUpdateGroupTypeResp) ProtoMessage() {} +func (*ForceUpdateGroupTypeResp) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{69} +} + +func (m *ForceUpdateGroupTypeResp) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ForceUpdateGroupTypeResp.Unmarshal(m, b) +} +func (m *ForceUpdateGroupTypeResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ForceUpdateGroupTypeResp.Marshal(b, m, deterministic) +} +func (m *ForceUpdateGroupTypeResp) XXX_Merge(src proto.Message) { + xxx_messageInfo_ForceUpdateGroupTypeResp.Merge(m, src) +} +func (m *ForceUpdateGroupTypeResp) XXX_Size() int { + return xxx_messageInfo_ForceUpdateGroupTypeResp.Size(m) +} +func (m *ForceUpdateGroupTypeResp) XXX_DiscardUnknown() { + xxx_messageInfo_ForceUpdateGroupTypeResp.DiscardUnknown(m) +} + +var xxx_messageInfo_ForceUpdateGroupTypeResp proto.InternalMessageInfo + +func (m *ForceUpdateGroupTypeResp) GetGroupId() int64 { + if m != nil { + return m.GroupId + } + return 0 +} + +func (m *ForceUpdateGroupTypeResp) GetGroupType() int32 { + if m != nil { + return m.GroupType + } + return 0 +} + +type ForceJoinGroupsReq struct { + Member *GroupMemberInfo `protobuf:"bytes,1,opt,name=member,proto3" json:"member,omitempty"` + GroupIds []int64 `protobuf:"varint,2,rep,packed,name=group_ids,json=groupIds,proto3" json:"group_ids,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ForceJoinGroupsReq) Reset() { *m = ForceJoinGroupsReq{} } +func (m *ForceJoinGroupsReq) String() string { return proto.CompactTextString(m) } +func (*ForceJoinGroupsReq) ProtoMessage() {} +func (*ForceJoinGroupsReq) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{70} +} + +func (m *ForceJoinGroupsReq) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ForceJoinGroupsReq.Unmarshal(m, b) +} +func (m *ForceJoinGroupsReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ForceJoinGroupsReq.Marshal(b, m, deterministic) +} +func (m *ForceJoinGroupsReq) XXX_Merge(src proto.Message) { + xxx_messageInfo_ForceJoinGroupsReq.Merge(m, src) +} +func (m *ForceJoinGroupsReq) XXX_Size() int { + return xxx_messageInfo_ForceJoinGroupsReq.Size(m) +} +func (m *ForceJoinGroupsReq) XXX_DiscardUnknown() { + xxx_messageInfo_ForceJoinGroupsReq.DiscardUnknown(m) +} + +var xxx_messageInfo_ForceJoinGroupsReq proto.InternalMessageInfo + +func (m *ForceJoinGroupsReq) GetMember() *GroupMemberInfo { + if m != nil { + return m.Member + } + return nil +} + +func (m *ForceJoinGroupsReq) GetGroupIds() []int64 { + if m != nil { + return m.GroupIds + } + return nil +} + +type ForceJoinGroupsResp struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ForceJoinGroupsResp) Reset() { *m = ForceJoinGroupsResp{} } +func (m *ForceJoinGroupsResp) String() string { return proto.CompactTextString(m) } +func (*ForceJoinGroupsResp) ProtoMessage() {} +func (*ForceJoinGroupsResp) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{71} +} + +func (m *ForceJoinGroupsResp) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ForceJoinGroupsResp.Unmarshal(m, b) +} +func (m *ForceJoinGroupsResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ForceJoinGroupsResp.Marshal(b, m, deterministic) +} +func (m *ForceJoinGroupsResp) XXX_Merge(src proto.Message) { + xxx_messageInfo_ForceJoinGroupsResp.Merge(m, src) +} +func (m *ForceJoinGroupsResp) XXX_Size() int { + return xxx_messageInfo_ForceJoinGroupsResp.Size(m) +} +func (m *ForceJoinGroupsResp) XXX_DiscardUnknown() { + xxx_messageInfo_ForceJoinGroupsResp.DiscardUnknown(m) +} + +var xxx_messageInfo_ForceJoinGroupsResp proto.InternalMessageInfo + +type ForceExitGroupsReq struct { + Member *GroupMemberInfo `protobuf:"bytes,1,opt,name=member,proto3" json:"member,omitempty"` + GroupIds []int64 `protobuf:"varint,2,rep,packed,name=group_ids,json=groupIds,proto3" json:"group_ids,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ForceExitGroupsReq) Reset() { *m = ForceExitGroupsReq{} } +func (m *ForceExitGroupsReq) String() string { return proto.CompactTextString(m) } +func (*ForceExitGroupsReq) ProtoMessage() {} +func (*ForceExitGroupsReq) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{72} +} + +func (m *ForceExitGroupsReq) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ForceExitGroupsReq.Unmarshal(m, b) +} +func (m *ForceExitGroupsReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ForceExitGroupsReq.Marshal(b, m, deterministic) +} +func (m *ForceExitGroupsReq) XXX_Merge(src proto.Message) { + xxx_messageInfo_ForceExitGroupsReq.Merge(m, src) +} +func (m *ForceExitGroupsReq) XXX_Size() int { + return xxx_messageInfo_ForceExitGroupsReq.Size(m) +} +func (m *ForceExitGroupsReq) XXX_DiscardUnknown() { + xxx_messageInfo_ForceExitGroupsReq.DiscardUnknown(m) +} + +var xxx_messageInfo_ForceExitGroupsReq proto.InternalMessageInfo + +func (m *ForceExitGroupsReq) GetMember() *GroupMemberInfo { + if m != nil { + return m.Member + } + return nil +} + +func (m *ForceExitGroupsReq) GetGroupIds() []int64 { + if m != nil { + return m.GroupIds + } + return nil +} + +type ForceExitGroupsResp struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ForceExitGroupsResp) Reset() { *m = ForceExitGroupsResp{} } +func (m *ForceExitGroupsResp) String() string { return proto.CompactTextString(m) } +func (*ForceExitGroupsResp) ProtoMessage() {} +func (*ForceExitGroupsResp) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{73} +} + +func (m *ForceExitGroupsResp) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ForceExitGroupsResp.Unmarshal(m, b) +} +func (m *ForceExitGroupsResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ForceExitGroupsResp.Marshal(b, m, deterministic) +} +func (m *ForceExitGroupsResp) XXX_Merge(src proto.Message) { + xxx_messageInfo_ForceExitGroupsResp.Merge(m, src) +} +func (m *ForceExitGroupsResp) XXX_Size() int { + return xxx_messageInfo_ForceExitGroupsResp.Size(m) +} +func (m *ForceExitGroupsResp) XXX_DiscardUnknown() { + xxx_messageInfo_ForceExitGroupsResp.DiscardUnknown(m) +} + +var xxx_messageInfo_ForceExitGroupsResp proto.InternalMessageInfo + +type ForceChangeOwnerReq struct { + GroupId int64 `protobuf:"varint,1,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"` + Member *GroupMemberInfo `protobuf:"bytes,2,opt,name=member,proto3" json:"member,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ForceChangeOwnerReq) Reset() { *m = ForceChangeOwnerReq{} } +func (m *ForceChangeOwnerReq) String() string { return proto.CompactTextString(m) } +func (*ForceChangeOwnerReq) ProtoMessage() {} +func (*ForceChangeOwnerReq) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{74} +} + +func (m *ForceChangeOwnerReq) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ForceChangeOwnerReq.Unmarshal(m, b) +} +func (m *ForceChangeOwnerReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ForceChangeOwnerReq.Marshal(b, m, deterministic) +} +func (m *ForceChangeOwnerReq) XXX_Merge(src proto.Message) { + xxx_messageInfo_ForceChangeOwnerReq.Merge(m, src) +} +func (m *ForceChangeOwnerReq) XXX_Size() int { + return xxx_messageInfo_ForceChangeOwnerReq.Size(m) +} +func (m *ForceChangeOwnerReq) XXX_DiscardUnknown() { + xxx_messageInfo_ForceChangeOwnerReq.DiscardUnknown(m) +} + +var xxx_messageInfo_ForceChangeOwnerReq proto.InternalMessageInfo + +func (m *ForceChangeOwnerReq) GetGroupId() int64 { + if m != nil { + return m.GroupId + } + return 0 +} + +func (m *ForceChangeOwnerReq) GetMember() *GroupMemberInfo { + if m != nil { + return m.Member + } + return nil +} + +type ForceChangeOwnerResp struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ForceChangeOwnerResp) Reset() { *m = ForceChangeOwnerResp{} } +func (m *ForceChangeOwnerResp) String() string { return proto.CompactTextString(m) } +func (*ForceChangeOwnerResp) ProtoMessage() {} +func (*ForceChangeOwnerResp) Descriptor() ([]byte, []int) { + return fileDescriptor_e10f4c9b19ad8eee, []int{75} +} + +func (m *ForceChangeOwnerResp) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ForceChangeOwnerResp.Unmarshal(m, b) +} +func (m *ForceChangeOwnerResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ForceChangeOwnerResp.Marshal(b, m, deterministic) +} +func (m *ForceChangeOwnerResp) XXX_Merge(src proto.Message) { + xxx_messageInfo_ForceChangeOwnerResp.Merge(m, src) +} +func (m *ForceChangeOwnerResp) XXX_Size() int { + return xxx_messageInfo_ForceChangeOwnerResp.Size(m) +} +func (m *ForceChangeOwnerResp) XXX_DiscardUnknown() { + xxx_messageInfo_ForceChangeOwnerResp.DiscardUnknown(m) +} + +var xxx_messageInfo_ForceChangeOwnerResp proto.InternalMessageInfo + +func init() { + proto.RegisterEnum("dtalk.group.GroupStatus", GroupStatus_name, GroupStatus_value) + proto.RegisterEnum("dtalk.group.GroupType", GroupType_name, GroupType_value) + proto.RegisterEnum("dtalk.group.GroupJoinType", GroupJoinType_name, GroupJoinType_value) + proto.RegisterEnum("dtalk.group.GroupMuteType", GroupMuteType_name, GroupMuteType_value) + proto.RegisterEnum("dtalk.group.GroupFriendType", GroupFriendType_name, GroupFriendType_value) + proto.RegisterEnum("dtalk.group.GroupMemberType", GroupMemberType_name, GroupMemberType_value) + proto.RegisterType((*GroupInfo)(nil), "dtalk.group.GroupInfo") + proto.RegisterType((*GroupMemberInfo)(nil), "dtalk.group.GroupMemberInfo") + proto.RegisterType((*GetGroupIdsRequest)(nil), "dtalk.group.GetGroupIdsRequest") + proto.RegisterType((*GetGroupIdsReply)(nil), "dtalk.group.GetGroupIdsReply") + proto.RegisterType((*CheckInGroupRequest)(nil), "dtalk.group.CheckInGroupRequest") + proto.RegisterType((*CheckInGroupReply)(nil), "dtalk.group.CheckInGroupReply") + proto.RegisterType((*GetMemberIdsRequest)(nil), "dtalk.group.GetMemberIdsRequest") + proto.RegisterType((*GetMemberIdsReply)(nil), "dtalk.group.GetMemberIdsReply") + proto.RegisterType((*CheckMuteRequest)(nil), "dtalk.group.CheckMuteRequest") + proto.RegisterType((*CheckMuteReply)(nil), "dtalk.group.CheckMuteReply") + proto.RegisterType((*GetGroupsReq)(nil), "dtalk.group.GetGroupsReq") + proto.RegisterType((*GetGroupsResp)(nil), "dtalk.group.GetGroupsResp") + proto.RegisterType((*GroupBizInfo)(nil), "dtalk.group.GroupBizInfo") + proto.RegisterType((*GroupMemberBizInfo)(nil), "dtalk.group.GroupMemberBizInfo") + proto.RegisterType((*CreateGroupReq)(nil), "dtalk.group.CreateGroupReq") + proto.RegisterType((*CreateGroupResp)(nil), "dtalk.group.CreateGroupResp") + proto.RegisterType((*InviteGroupMembersReq)(nil), "dtalk.group.InviteGroupMembersReq") + proto.RegisterType((*InviteGroupMembersResp)(nil), "dtalk.group.InviteGroupMembersResp") + proto.RegisterType((*GroupExitReq)(nil), "dtalk.group.GroupExitReq") + proto.RegisterType((*GroupExitResp)(nil), "dtalk.group.GroupExitResp") + proto.RegisterType((*GroupDisbandReq)(nil), "dtalk.group.GroupDisbandReq") + proto.RegisterType((*GroupDisbandResp)(nil), "dtalk.group.GroupDisbandResp") + proto.RegisterType((*GroupRemoveReq)(nil), "dtalk.group.GroupRemoveReq") + proto.RegisterType((*GroupRemoveResp)(nil), "dtalk.group.GroupRemoveResp") + proto.RegisterType((*ChangeOwnerReq)(nil), "dtalk.group.ChangeOwnerReq") + proto.RegisterType((*ChangeOwnerResp)(nil), "dtalk.group.ChangeOwnerResp") + proto.RegisterType((*UpdateGroupNameReq)(nil), "dtalk.group.UpdateGroupNameReq") + proto.RegisterType((*UpdateGroupNameResp)(nil), "dtalk.group.UpdateGroupNameResp") + proto.RegisterType((*UpdateGroupAvatarReq)(nil), "dtalk.group.UpdateGroupAvatarReq") + proto.RegisterType((*UpdateGroupAvatarResp)(nil), "dtalk.group.UpdateGroupAvatarResp") + proto.RegisterType((*UpdateGroupJoinTypeReq)(nil), "dtalk.group.UpdateGroupJoinTypeReq") + proto.RegisterType((*UpdateGroupJoinTypeResp)(nil), "dtalk.group.UpdateGroupJoinTypeResp") + proto.RegisterType((*UpdateGroupFriendTypeReq)(nil), "dtalk.group.UpdateGroupFriendTypeReq") + proto.RegisterType((*UpdateGroupFriendTypeResp)(nil), "dtalk.group.UpdateGroupFriendTypeResp") + proto.RegisterType((*UpdateGroupMuteTypeReq)(nil), "dtalk.group.UpdateGroupMuteTypeReq") + proto.RegisterType((*UpdateGroupMuteTypeResp)(nil), "dtalk.group.UpdateGroupMuteTypeResp") + proto.RegisterType((*UpdateGroupMemberNameReq)(nil), "dtalk.group.UpdateGroupMemberNameReq") + proto.RegisterType((*UpdateGroupMemberNameResp)(nil), "dtalk.group.UpdateGroupMemberNameResp") + proto.RegisterType((*SetAdminReq)(nil), "dtalk.group.SetAdminReq") + proto.RegisterType((*SetAdminResp)(nil), "dtalk.group.SetAdminResp") + proto.RegisterType((*UpdateGroupMemberMuteTimeReq)(nil), "dtalk.group.UpdateGroupMemberMuteTimeReq") + proto.RegisterType((*UpdateGroupMemberMuteTimeResp)(nil), "dtalk.group.UpdateGroupMemberMuteTimeResp") + proto.RegisterType((*GetPriGroupInfoReq)(nil), "dtalk.group.GetPriGroupInfoReq") + proto.RegisterType((*GetPriGroupInfoResp)(nil), "dtalk.group.GetPriGroupInfoResp") + proto.RegisterType((*GetPubGroupInfoReq)(nil), "dtalk.group.GetPubGroupInfoReq") + proto.RegisterType((*GetPubGroupInfoResp)(nil), "dtalk.group.GetPubGroupInfoResp") + proto.RegisterType((*GetGroupListReq)(nil), "dtalk.group.GetGroupListReq") + proto.RegisterType((*GetGroupListResp)(nil), "dtalk.group.GetGroupListResp") + proto.RegisterType((*GetGroupMemberListReq)(nil), "dtalk.group.GetGroupMemberListReq") + proto.RegisterType((*GetGroupMemberListResp)(nil), "dtalk.group.GetGroupMemberListResp") + proto.RegisterType((*GetGroupMemberInfoReq)(nil), "dtalk.group.GetGroupMemberInfoReq") + proto.RegisterType((*GetGroupMemberInfoResp)(nil), "dtalk.group.GetGroupMemberInfoResp") + proto.RegisterType((*GetMuteListReq)(nil), "dtalk.group.GetMuteListReq") + proto.RegisterType((*GetMuteListResp)(nil), "dtalk.group.GetMuteListResp") + proto.RegisterType((*ForceAddMemberReq)(nil), "dtalk.group.ForceAddMemberReq") + proto.RegisterType((*ForceAddMemberResp)(nil), "dtalk.group.ForceAddMemberResp") + proto.RegisterType((*ForceAddMembersReq)(nil), "dtalk.group.ForceAddMembersReq") + proto.RegisterType((*ForceAddMembersResp)(nil), "dtalk.group.ForceAddMembersResp") + proto.RegisterType((*ForceDeleteMemberReq)(nil), "dtalk.group.ForceDeleteMemberReq") + proto.RegisterType((*ForceDeleteMemberResp)(nil), "dtalk.group.ForceDeleteMemberResp") + proto.RegisterType((*ForceDeleteMembersReq)(nil), "dtalk.group.ForceDeleteMembersReq") + proto.RegisterType((*ForceDeleteMembersResp)(nil), "dtalk.group.ForceDeleteMembersResp") + proto.RegisterType((*GetMemberReq)(nil), "dtalk.group.GetMemberReq") + proto.RegisterType((*GetMemberResp)(nil), "dtalk.group.GetMemberResp") + proto.RegisterType((*GetGroupInfoReq)(nil), "dtalk.group.GetGroupInfoReq") + proto.RegisterType((*GetGroupInfoResp)(nil), "dtalk.group.GetGroupInfoResp") + proto.RegisterType((*ForceDisbandGroupReq)(nil), "dtalk.group.ForceDisbandGroupReq") + proto.RegisterType((*ForceDisbandGroupResp)(nil), "dtalk.group.ForceDisbandGroupResp") + proto.RegisterType((*ForceUpdateGroupTypeReq)(nil), "dtalk.group.ForceUpdateGroupTypeReq") + proto.RegisterType((*ForceUpdateGroupTypeResp)(nil), "dtalk.group.ForceUpdateGroupTypeResp") + proto.RegisterType((*ForceJoinGroupsReq)(nil), "dtalk.group.ForceJoinGroupsReq") + proto.RegisterType((*ForceJoinGroupsResp)(nil), "dtalk.group.ForceJoinGroupsResp") + proto.RegisterType((*ForceExitGroupsReq)(nil), "dtalk.group.ForceExitGroupsReq") + proto.RegisterType((*ForceExitGroupsResp)(nil), "dtalk.group.ForceExitGroupsResp") + proto.RegisterType((*ForceChangeOwnerReq)(nil), "dtalk.group.ForceChangeOwnerReq") + proto.RegisterType((*ForceChangeOwnerResp)(nil), "dtalk.group.ForceChangeOwnerResp") +} + +func init() { proto.RegisterFile("group.proto", fileDescriptor_e10f4c9b19ad8eee) } + +var fileDescriptor_e10f4c9b19ad8eee = []byte{ + // 2496 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x5a, 0x4f, 0x53, 0x1b, 0xc9, + 0x15, 0xdf, 0x91, 0x90, 0x40, 0x4f, 0x20, 0x44, 0x63, 0xc1, 0x30, 0x80, 0xc1, 0x63, 0x3b, 0x21, + 0x24, 0x05, 0x5e, 0xbc, 0x8e, 0x2b, 0x87, 0xd4, 0x16, 0x18, 0x19, 0x0b, 0x83, 0x84, 0x85, 0x5c, + 0x8e, 0x5d, 0xb5, 0x51, 0x04, 0x1a, 0xd8, 0x59, 0x90, 0xd4, 0x51, 0x8f, 0x1c, 0x93, 0x5b, 0x2e, + 0xb9, 0x24, 0xa7, 0x54, 0x8e, 0xf9, 0x08, 0xb9, 0xe6, 0x9c, 0x63, 0xaa, 0xf2, 0xa9, 0x52, 0xd3, + 0xdd, 0x33, 0xd3, 0xdd, 0xd3, 0x23, 0x69, 0xad, 0xdd, 0xdc, 0x50, 0xbf, 0x3f, 0xfd, 0xfa, 0xf5, + 0x9b, 0xf7, 0xe7, 0xd7, 0x40, 0xfe, 0xba, 0xdf, 0x1b, 0xe0, 0x1d, 0xdc, 0xef, 0x79, 0x3d, 0x94, + 0x6f, 0x7b, 0xad, 0xdb, 0x9b, 0x1d, 0xba, 0x64, 0x1f, 0x41, 0xee, 0xc8, 0xff, 0xa3, 0xd2, 0xbd, + 0xea, 0xa1, 0x02, 0xa4, 0xdc, 0xb6, 0x69, 0x6c, 0x1a, 0x5b, 0xe9, 0x7a, 0xca, 0x6d, 0x23, 0x04, + 0x53, 0xdd, 0x56, 0xc7, 0x31, 0x53, 0x9b, 0xc6, 0x56, 0xae, 0x4e, 0xff, 0x46, 0x4b, 0x90, 0x6d, + 0x7d, 0x6c, 0x79, 0xad, 0xbe, 0x99, 0xa6, 0xab, 0xfc, 0x97, 0xfd, 0x0c, 0xe6, 0xa9, 0xa2, 0x53, + 0xa7, 0x73, 0xe1, 0xf4, 0x15, 0x75, 0xb9, 0x24, 0x75, 0xf6, 0x97, 0x80, 0x8e, 0x1c, 0x8f, 0x99, + 0xd0, 0x26, 0x75, 0xe7, 0xf7, 0x03, 0x87, 0x78, 0x68, 0x15, 0x72, 0x1d, 0xaa, 0xa7, 0x19, 0x2a, + 0x98, 0x61, 0x0b, 0x95, 0xb6, 0xbd, 0x0b, 0x45, 0x49, 0x04, 0xdf, 0xde, 0xf9, 0x02, 0xf4, 0x3c, + 0x4d, 0xb7, 0x4d, 0x4c, 0x63, 0x33, 0xbd, 0x95, 0xae, 0xcf, 0x5c, 0x73, 0x0e, 0xfb, 0x14, 0x16, + 0x5f, 0x7c, 0xeb, 0x5c, 0xde, 0x54, 0xba, 0x54, 0x68, 0x9c, 0x4d, 0xd0, 0x0a, 0xcc, 0x04, 0x0a, + 0xa9, 0xbd, 0xe9, 0xfa, 0x34, 0xd7, 0x67, 0x6f, 0xc1, 0x82, 0xac, 0xce, 0x37, 0x60, 0x11, 0x32, + 0x2e, 0x69, 0xf6, 0x6e, 0xa8, 0xa2, 0x99, 0xfa, 0x94, 0x4b, 0x6a, 0x37, 0xf6, 0x13, 0x58, 0x3c, + 0x72, 0x3c, 0xee, 0x91, 0xe8, 0x74, 0xa2, 0x6e, 0x43, 0xd6, 0xbd, 0x07, 0x0b, 0xb2, 0x84, 0xaf, + 0x7b, 0x1d, 0x20, 0x34, 0x94, 0x9d, 0x2e, 0x57, 0xcf, 0x05, 0x96, 0x12, 0xfb, 0x18, 0x8a, 0xd4, + 0x9e, 0xd3, 0x81, 0xe7, 0x4c, 0x7a, 0xb6, 0xc7, 0x50, 0x10, 0x74, 0x25, 0x1e, 0xec, 0x3e, 0xcc, + 0x06, 0x57, 0xe0, 0x9f, 0x4a, 0xbd, 0x69, 0xfb, 0x6b, 0x98, 0x13, 0xe8, 0x04, 0xa3, 0x1d, 0xc8, + 0xd2, 0x2d, 0x98, 0xf9, 0xf9, 0xbd, 0xa5, 0x1d, 0x21, 0x08, 0x77, 0xc2, 0x08, 0xac, 0x73, 0x2e, + 0xfb, 0xbf, 0x59, 0x98, 0xa5, 0xab, 0x07, 0xee, 0x1f, 0xb5, 0xa1, 0xb9, 0x0c, 0xd3, 0x9d, 0x56, + 0xff, 0x26, 0x38, 0x42, 0xae, 0x9e, 0xf5, 0x7f, 0x56, 0xa2, 0x20, 0x4b, 0x6b, 0x63, 0x76, 0x4a, + 0x8c, 0x59, 0xc1, 0xb1, 0xdd, 0x41, 0xc7, 0xcc, 0x6c, 0x1a, 0x5b, 0x99, 0xc0, 0xb1, 0xd5, 0x41, + 0x07, 0x3d, 0x86, 0x02, 0x27, 0x77, 0x5a, 0x9f, 0xdc, 0xce, 0xa0, 0x63, 0x66, 0x29, 0xcb, 0x1c, + 0x5b, 0x3d, 0x65, 0x8b, 0x68, 0x0d, 0x72, 0x6e, 0xd7, 0xeb, 0xf7, 0xda, 0x83, 0x4b, 0xc7, 0x9c, + 0xa6, 0x1b, 0x44, 0x0b, 0xe8, 0x09, 0x64, 0x89, 0xd7, 0xf2, 0x06, 0xc4, 0x9c, 0xd9, 0x34, 0xb6, + 0x0a, 0x7b, 0x66, 0xfc, 0xe4, 0xe7, 0x94, 0x5e, 0xe7, 0x7c, 0xfe, 0xf5, 0xf4, 0xfe, 0xd0, 0x65, + 0x57, 0x97, 0xa3, 0xea, 0xa6, 0xe9, 0xef, 0x4a, 0x1b, 0x6d, 0x40, 0xfe, 0xb2, 0xef, 0xb4, 0x3c, + 0xa7, 0xe9, 0xb9, 0x1d, 0xc7, 0x04, 0xea, 0x0e, 0x60, 0x4b, 0x0d, 0xb7, 0xe3, 0xf8, 0x0c, 0x03, + 0xdc, 0x0e, 0x19, 0xf2, 0x8c, 0x81, 0x2d, 0x51, 0x86, 0xe7, 0x90, 0xfb, 0xae, 0xe7, 0x76, 0x9b, + 0xde, 0x1d, 0x76, 0xcc, 0x59, 0x6a, 0x91, 0x15, 0xb7, 0xe8, 0xb8, 0xe7, 0x76, 0x1b, 0x77, 0xd8, + 0xa9, 0xcf, 0x7c, 0xc7, 0xff, 0xf2, 0x05, 0x3b, 0x03, 0x5f, 0xaf, 0x2f, 0x38, 0x97, 0x24, 0xe8, + 0xc7, 0x0d, 0x13, 0xec, 0xf0, 0xbf, 0xd0, 0xaf, 0x21, 0x7f, 0xd5, 0x77, 0x9d, 0x6e, 0x9b, 0x89, + 0x16, 0xa8, 0xe8, 0x5a, 0x5c, 0xf4, 0x25, 0x65, 0xa2, 0xc2, 0x70, 0x15, 0xfe, 0xed, 0x7b, 0x83, + 0xee, 0xeb, 0xdf, 0xd0, 0x3c, 0x75, 0xff, 0xb4, 0xff, 0xdb, 0xbf, 0x9f, 0x55, 0xc8, 0xb5, 0xda, + 0x1d, 0xb7, 0x4b, 0x69, 0x45, 0x4a, 0x9b, 0xa1, 0x0b, 0x3e, 0x71, 0x19, 0xa6, 0xf7, 0xcb, 0xe7, + 0xcd, 0x1b, 0xe7, 0xce, 0x5c, 0x60, 0x97, 0xbe, 0x5f, 0x3e, 0x7f, 0xed, 0xdc, 0xf9, 0x0a, 0xf1, + 0xe0, 0xa2, 0x49, 0x83, 0x04, 0x31, 0xf7, 0xe2, 0xc1, 0x45, 0xd5, 0x8f, 0x93, 0x6d, 0x98, 0xa2, + 0x36, 0x2e, 0x52, 0x1b, 0x35, 0x31, 0x4a, 0xad, 0xa3, 0x3c, 0xe8, 0x19, 0x64, 0xe8, 0xad, 0x98, + 0xf7, 0x36, 0x8d, 0xad, 0xfc, 0xde, 0x86, 0xc6, 0x17, 0x34, 0x4a, 0x78, 0x00, 0xd7, 0x19, 0x37, + 0x7a, 0x0e, 0x59, 0xec, 0xf4, 0x49, 0xaf, 0x6b, 0x96, 0xc6, 0x93, 0xe3, 0xec, 0xe8, 0x57, 0x30, + 0xcd, 0xc2, 0x8e, 0x98, 0x4b, 0xf4, 0x13, 0x1a, 0x29, 0x19, 0xf0, 0xdb, 0xff, 0x36, 0x00, 0xc5, + 0xe9, 0x43, 0xd2, 0x10, 0xff, 0xda, 0x52, 0xb1, 0xcc, 0x2d, 0x7e, 0x54, 0x4f, 0xb8, 0xb3, 0xa6, + 0x92, 0x2e, 0x94, 0xed, 0x26, 0xb8, 0x6c, 0x35, 0x08, 0x21, 0x3f, 0x34, 0x33, 0x74, 0x47, 0x16, + 0x26, 0x7e, 0x60, 0xae, 0x06, 0x81, 0xe9, 0x13, 0xb3, 0x8c, 0x48, 0x83, 0xcf, 0xed, 0x38, 0xf6, + 0x7f, 0x0c, 0x28, 0xbc, 0xa0, 0x51, 0x1e, 0x64, 0xf0, 0xd0, 0x24, 0x43, 0x30, 0xe9, 0x19, 0x00, + 0x3b, 0x11, 0x35, 0x2c, 0x35, 0xf4, 0x16, 0x59, 0xbd, 0xa0, 0x21, 0xb6, 0x17, 0x5c, 0x65, 0x9a, + 0x5e, 0x49, 0xe2, 0x51, 0xc4, 0x7b, 0xfc, 0x65, 0x74, 0x1d, 0x53, 0xf4, 0x3a, 0x86, 0x4b, 0x85, + 0x77, 0xf1, 0x0b, 0x98, 0x97, 0x0e, 0x42, 0xf0, 0xb0, 0x72, 0x80, 0xa1, 0x54, 0xe9, 0x7e, 0x74, + 0x39, 0x37, 0xd3, 0x47, 0x13, 0xee, 0x90, 0xbb, 0x5b, 0x07, 0x70, 0xa9, 0x4c, 0x3f, 0x4a, 0x8e, + 0x39, 0xbe, 0xc2, 0xc8, 0x42, 0x31, 0x49, 0xab, 0xc5, 0xc4, 0x84, 0x25, 0xdd, 0x8e, 0x04, 0xdb, + 0x2f, 0x79, 0x46, 0x2e, 0x7f, 0x72, 0xbd, 0x11, 0x26, 0xac, 0x42, 0x8e, 0x45, 0x6d, 0x64, 0xc1, + 0x0c, 0x5b, 0xa8, 0xb4, 0xed, 0x79, 0x98, 0x13, 0xf4, 0x10, 0x6c, 0x57, 0x78, 0xe7, 0x70, 0xe8, + 0x92, 0x8b, 0x56, 0xb7, 0x3d, 0x89, 0x6e, 0x04, 0x45, 0x59, 0x15, 0xc1, 0xf6, 0x35, 0x14, 0xb8, + 0xaf, 0x3b, 0xbd, 0x8f, 0xce, 0x04, 0xda, 0x47, 0xb9, 0xae, 0xc6, 0xcf, 0x11, 0x6c, 0x44, 0xb0, + 0x52, 0x60, 0x0c, 0xb5, 0xc0, 0xc8, 0x0a, 0x53, 0xaa, 0x42, 0xc7, 0x2f, 0xc6, 0xad, 0xee, 0xb5, + 0x53, 0xf3, 0x43, 0x6e, 0x12, 0xcb, 0xa5, 0x76, 0x20, 0xad, 0xf4, 0x53, 0x0b, 0x30, 0x2f, 0x6d, + 0x43, 0xb0, 0xfd, 0x27, 0x03, 0xd0, 0x5b, 0x5a, 0x34, 0xe8, 0x89, 0xfc, 0xe4, 0x38, 0xc9, 0xf6, + 0xba, 0xf4, 0xb1, 0x01, 0x79, 0x3c, 0xb8, 0xb8, 0x75, 0x2f, 0x59, 0x26, 0x66, 0x85, 0x19, 0xd8, + 0x92, 0xbf, 0x9f, 0x5d, 0x82, 0xc5, 0x98, 0x09, 0x04, 0xdb, 0x57, 0x70, 0x4f, 0x58, 0xde, 0xa7, + 0x85, 0x7c, 0x12, 0xdb, 0x92, 0xfa, 0xd9, 0x65, 0x28, 0x69, 0xf6, 0x21, 0xd8, 0xfe, 0xbb, 0x01, + 0x4b, 0x02, 0x25, 0x2c, 0x95, 0x13, 0xd8, 0x70, 0x00, 0xf3, 0x4c, 0x2e, 0x2a, 0xcd, 0xe9, 0x91, + 0xa5, 0x79, 0xee, 0x5a, 0xfc, 0x69, 0xaf, 0xc0, 0xb2, 0xd6, 0x2a, 0x82, 0xed, 0x7f, 0x18, 0x60, + 0x0a, 0x34, 0xa1, 0xd0, 0x4e, 0x60, 0xf3, 0x2b, 0x58, 0x60, 0x72, 0x62, 0x71, 0x4f, 0x8f, 0x51, + 0xdc, 0xd9, 0x51, 0xa3, 0x05, 0x7b, 0x15, 0x56, 0x12, 0xac, 0x8b, 0x7b, 0x3b, 0xec, 0x2f, 0x7e, + 0x08, 0x6f, 0x47, 0xfd, 0x4c, 0x7a, 0x64, 0x3f, 0xc3, 0xbc, 0x1d, 0xfc, 0x54, 0xbc, 0x1d, 0x59, + 0x45, 0xb0, 0x4d, 0x24, 0x67, 0xb3, 0x0c, 0x3a, 0xe9, 0x07, 0xb4, 0x01, 0xf9, 0x20, 0x8f, 0x44, + 0xdf, 0x11, 0xcf, 0x1d, 0xf4, 0x63, 0x91, 0x7d, 0x28, 0x6e, 0x4a, 0xb0, 0xfd, 0x4f, 0x03, 0xf2, + 0xe7, 0x8e, 0xb7, 0xef, 0xb7, 0x46, 0x3f, 0x56, 0x16, 0x89, 0xe2, 0x81, 0xb3, 0x8c, 0xdd, 0x1b, + 0xb0, 0xcb, 0x88, 0x16, 0xec, 0x02, 0xcc, 0x46, 0xd6, 0x12, 0x6c, 0xff, 0xcd, 0x80, 0xb5, 0xd8, + 0xe1, 0x4e, 0x79, 0xdf, 0xf0, 0xe3, 0xe5, 0x73, 0xb9, 0x5d, 0x99, 0x92, 0xdb, 0x15, 0xfb, 0x03, + 0xac, 0x0f, 0xb1, 0x89, 0x60, 0xb1, 0x5f, 0x33, 0xbe, 0x67, 0xbf, 0xd6, 0xa1, 0x33, 0xf1, 0x59, + 0xdf, 0x8d, 0xe6, 0xa2, 0xc9, 0x62, 0xa7, 0xed, 0x12, 0x7c, 0xdb, 0xba, 0xa3, 0x45, 0x28, 0x4d, + 0x8b, 0x10, 0xf0, 0xa5, 0xea, 0xa0, 0x63, 0xbf, 0xa4, 0x53, 0xaa, 0xbc, 0x1d, 0xc1, 0x68, 0x17, + 0x32, 0x54, 0x3f, 0xdd, 0x2c, 0xbf, 0xb7, 0x12, 0x37, 0x3f, 0x6c, 0x6d, 0x19, 0x94, 0x70, 0xc2, + 0xcc, 0x1e, 0x5c, 0xfc, 0x10, 0x66, 0x07, 0x56, 0x49, 0xda, 0x3e, 0xc7, 0xaa, 0x1d, 0x98, 0x0f, + 0x46, 0xd1, 0x13, 0x97, 0xd0, 0xce, 0x45, 0xda, 0xd7, 0x50, 0xf6, 0x2d, 0x47, 0xe8, 0x02, 0xe3, + 0x27, 0x18, 0x7d, 0xa9, 0x4c, 0xaf, 0x43, 0x76, 0x0d, 0x06, 0xd8, 0x1a, 0x94, 0x02, 0x35, 0xec, + 0x96, 0x83, 0xcd, 0x3f, 0xd7, 0x1f, 0xe7, 0xb0, 0xa4, 0x53, 0x38, 0x59, 0xa4, 0xdd, 0xaa, 0x56, + 0x4e, 0x1a, 0x6c, 0x43, 0x1b, 0x8d, 0x37, 0xea, 0x11, 0xc2, 0x5b, 0x7d, 0x0e, 0x59, 0xc6, 0xc5, + 0xaf, 0x75, 0xf4, 0x54, 0xc4, 0xd8, 0xed, 0x57, 0x50, 0x38, 0x72, 0x3c, 0xff, 0xc3, 0x9b, 0xd4, + 0xbf, 0x27, 0x34, 0x4e, 0x22, 0x4d, 0x93, 0x39, 0xf6, 0x35, 0x2c, 0xbc, 0xec, 0xf5, 0x2f, 0x9d, + 0xfd, 0x76, 0x9b, 0x71, 0xf0, 0xb8, 0xfb, 0x2c, 0x50, 0xe6, 0x1e, 0x20, 0x55, 0x19, 0xed, 0x6b, + 0x95, 0xd5, 0x51, 0x83, 0x81, 0x30, 0xb2, 0xa4, 0xbe, 0xcf, 0xc8, 0x52, 0x82, 0xc5, 0xd8, 0x46, + 0x04, 0xdb, 0x55, 0xb8, 0x47, 0x97, 0x0f, 0x9d, 0x5b, 0xc7, 0x73, 0x26, 0x3f, 0xe5, 0x32, 0x94, + 0x34, 0xfa, 0x08, 0xb6, 0xdf, 0x68, 0x08, 0x63, 0x0c, 0x41, 0xc3, 0x3a, 0x6b, 0x13, 0x96, 0x74, + 0x2a, 0xf9, 0x94, 0x13, 0x00, 0x70, 0x93, 0x9c, 0xe6, 0x2f, 0x29, 0x0a, 0x81, 0x45, 0xc7, 0x40, + 0x26, 0x04, 0x44, 0xd5, 0xd8, 0x47, 0x30, 0x27, 0xd4, 0xc0, 0x4a, 0x10, 0x9b, 0xf2, 0x22, 0xda, + 0x02, 0xb1, 0x52, 0x56, 0xa3, 0x3e, 0x40, 0x5d, 0x56, 0x38, 0x1b, 0x41, 0x21, 0xce, 0xc4, 0x4a, + 0x2d, 0x7a, 0x02, 0x8b, 0xc2, 0xd2, 0x31, 0x1f, 0xb7, 0xf9, 0x6c, 0xae, 0x23, 0xa1, 0xaf, 0xa0, + 0x24, 0x2c, 0xbf, 0x0d, 0x81, 0x25, 0x3e, 0xb2, 0xeb, 0x89, 0xf6, 0xcf, 0xa3, 0x24, 0x1c, 0x64, + 0x98, 0x44, 0x77, 0xd8, 0xff, 0x32, 0x04, 0x80, 0x37, 0xc8, 0x10, 0xc9, 0xde, 0xdb, 0xe0, 0xe8, + 0x76, 0xd3, 0xf9, 0xe4, 0x12, 0x8f, 0xfa, 0x6e, 0xa6, 0xce, 0x70, 0x80, 0xb2, 0xbf, 0xe2, 0xc7, + 0x02, 0x63, 0x10, 0x7a, 0x27, 0x36, 0xfd, 0x53, 0x6f, 0x3d, 0x80, 0x59, 0x46, 0x96, 0x20, 0x42, + 0xa6, 0x93, 0xb5, 0xfd, 0xe8, 0x11, 0x14, 0x18, 0x4b, 0x88, 0xcb, 0x65, 0x28, 0x13, 0x13, 0xac, + 0x31, 0x70, 0xce, 0x7e, 0x15, 0x7c, 0x10, 0x6c, 0xf8, 0x0c, 0x91, 0x8a, 0x21, 0x61, 0x5a, 0x82, + 0x6c, 0x0f, 0x3b, 0x51, 0x3a, 0xca, 0xf4, 0xb0, 0x23, 0x7e, 0x0a, 0x92, 0x26, 0x82, 0xed, 0x73, + 0x58, 0xa6, 0x04, 0xa1, 0xf5, 0x18, 0xa3, 0x1b, 0x5e, 0x8f, 0xc1, 0x22, 0x19, 0x01, 0xfe, 0xb0, + 0x1b, 0x60, 0xea, 0x95, 0x0e, 0xc5, 0x26, 0x46, 0x69, 0x0d, 0xd2, 0x93, 0x1f, 0x39, 0x11, 0x50, + 0xfc, 0x95, 0x92, 0xe8, 0x87, 0xa7, 0x20, 0xce, 0x2b, 0xa3, 0xfb, 0x29, 0x05, 0xdd, 0x0f, 0xd2, + 0x93, 0xb8, 0x91, 0x90, 0x1e, 0xcb, 0x9f, 0x5c, 0xef, 0xff, 0xb2, 0xbf, 0xb8, 0x11, 0x9d, 0x53, + 0xd9, 0xf2, 0xf8, 0x13, 0x7c, 0x64, 0x5b, 0x6a, 0x7c, 0xdb, 0xec, 0x25, 0x1e, 0x75, 0xca, 0x08, + 0xbf, 0xfd, 0x1b, 0xc8, 0x0b, 0xe0, 0x32, 0x5a, 0x86, 0xc5, 0xa3, 0x7a, 0xed, 0xed, 0x59, 0xf3, + 0xbc, 0xb1, 0xdf, 0x78, 0x7b, 0xde, 0xac, 0xd6, 0xea, 0xa7, 0xfb, 0x27, 0xc5, 0x2f, 0xd0, 0x12, + 0x20, 0x89, 0x70, 0x70, 0x52, 0x7b, 0xf1, 0xba, 0x68, 0x20, 0x13, 0xee, 0x49, 0xeb, 0x87, 0x95, + 0xf3, 0x83, 0xfd, 0xea, 0x61, 0x31, 0xb5, 0x7d, 0xcc, 0x9f, 0x8c, 0x68, 0x06, 0x29, 0xc1, 0x02, + 0x63, 0x6b, 0xbc, 0x3f, 0x2b, 0x47, 0x5a, 0x11, 0x14, 0x84, 0xe5, 0x72, 0xb5, 0x51, 0x34, 0x94, + 0xb5, 0xc3, 0xf2, 0x59, 0x31, 0xb5, 0xfd, 0x5b, 0x0e, 0x06, 0x05, 0xf3, 0x6a, 0x64, 0xe7, 0x71, + 0xad, 0x52, 0x65, 0x9c, 0xfb, 0xd5, 0xf7, 0xc5, 0x2f, 0xd0, 0x0a, 0x94, 0x62, 0x84, 0xc3, 0xd3, + 0x4a, 0xb5, 0x68, 0x68, 0x49, 0x67, 0x67, 0x27, 0xef, 0x8b, 0xa9, 0xed, 0x17, 0x5c, 0x7f, 0x30, + 0xa1, 0x45, 0xfa, 0x4f, 0xdf, 0x36, 0xca, 0x5a, 0xfd, 0x02, 0x81, 0xe9, 0xdf, 0x3e, 0xe6, 0xc0, + 0x4e, 0x34, 0x9a, 0xa2, 0x55, 0x58, 0x66, 0xdc, 0x2f, 0xeb, 0x95, 0x72, 0xf5, 0x90, 0xf3, 0x9f, + 0x9c, 0xd4, 0xde, 0x15, 0xbf, 0x40, 0x16, 0x2c, 0xc5, 0x89, 0x87, 0xe5, 0xea, 0xfb, 0xa2, 0xb1, + 0xfd, 0x67, 0x43, 0x7a, 0x27, 0xa3, 0xca, 0xd6, 0xc0, 0xe4, 0x5b, 0x97, 0x4f, 0x0f, 0xca, 0x75, + 0xc5, 0x95, 0xe1, 0x56, 0x22, 0x35, 0x38, 0xba, 0x96, 0x58, 0x7b, 0x57, 0x2d, 0xd7, 0x8b, 0xa9, + 0x04, 0x62, 0xe3, 0x55, 0xb9, 0x5e, 0x84, 0xbd, 0xbf, 0x9a, 0x90, 0xa1, 0x86, 0xa0, 0x53, 0xc8, + 0x0b, 0xef, 0x69, 0x48, 0x69, 0x72, 0x62, 0x8f, 0x73, 0xd6, 0x7a, 0x32, 0x03, 0xbe, 0xbd, 0x43, + 0x67, 0x30, 0x2b, 0x3e, 0x8f, 0xa1, 0x4d, 0x89, 0x5d, 0xf3, 0x10, 0x67, 0xdd, 0x1f, 0xc2, 0xc1, + 0x35, 0x8a, 0x8f, 0x62, 0x8a, 0x46, 0xcd, 0x0b, 0x9b, 0xa2, 0x31, 0xfe, 0xa2, 0x76, 0x04, 0xb9, + 0xf0, 0x99, 0x0b, 0xad, 0xc7, 0xb7, 0x17, 0x9e, 0xd2, 0xac, 0xd5, 0x24, 0xb2, 0xaf, 0xe8, 0x00, + 0x72, 0xe1, 0x43, 0x17, 0x5a, 0xd1, 0x3a, 0xc6, 0x37, 0xca, 0xb2, 0x92, 0x48, 0x04, 0x73, 0x1d, + 0xcc, 0xc2, 0xb8, 0x8e, 0xb0, 0x15, 0x89, 0xeb, 0x10, 0x9a, 0x8b, 0xd7, 0xd1, 0x83, 0x1c, 0xc5, + 0xf6, 0xd7, 0xf4, 0x77, 0xc4, 0x6a, 0x6f, 0xd2, 0x0d, 0x06, 0xb5, 0xf6, 0x15, 0xe4, 0x05, 0x8c, + 0x1a, 0x29, 0x0e, 0x90, 0x60, 0x78, 0x6b, 0x2d, 0x99, 0x48, 0x30, 0xfa, 0x06, 0x50, 0x1c, 0x4d, + 0x46, 0xb6, 0x24, 0xa3, 0x05, 0xb8, 0xad, 0x87, 0x23, 0x79, 0xb8, 0xe7, 0x02, 0x28, 0x19, 0x69, + 0x86, 0x32, 0x0e, 0x55, 0x5b, 0x56, 0x12, 0x89, 0x7b, 0x4e, 0x80, 0x8c, 0x91, 0x26, 0xeb, 0x46, + 0xc0, 0xb4, 0xea, 0x39, 0x05, 0x6b, 0xf6, 0x3d, 0x27, 0x40, 0xc0, 0x8a, 0xe7, 0x64, 0x14, 0xda, + 0x5a, 0x4b, 0x26, 0xf2, 0x3b, 0x88, 0x32, 0xba, 0x7a, 0x07, 0x52, 0x4d, 0x51, 0xef, 0x40, 0x2e, + 0x04, 0xa8, 0x01, 0xf3, 0x0a, 0x8e, 0xaa, 0x7c, 0xe2, 0x71, 0xa0, 0xd7, 0xda, 0x1c, 0xce, 0x40, + 0x30, 0xfa, 0x00, 0x0b, 0x31, 0x78, 0x14, 0x3d, 0x48, 0x12, 0x0b, 0x61, 0x5a, 0xcb, 0x1e, 0xc5, + 0x42, 0x30, 0xfa, 0x9d, 0x84, 0xfc, 0x86, 0xa5, 0xe1, 0x61, 0x92, 0xa8, 0x00, 0xc1, 0x5a, 0x8f, + 0x46, 0x33, 0x11, 0x8c, 0xae, 0x24, 0x70, 0x57, 0xc8, 0xeb, 0x8f, 0x93, 0xc4, 0x25, 0xd0, 0xd4, + 0xfa, 0xc9, 0x38, 0x6c, 0xb1, 0x93, 0x84, 0x45, 0x28, 0xf1, 0x24, 0x02, 0xbc, 0x99, 0x7c, 0x12, + 0x11, 0x6d, 0x54, 0x4e, 0x22, 0x0c, 0x01, 0x89, 0x27, 0x91, 0x10, 0xc9, 0xe4, 0x93, 0xc8, 0x18, + 0x22, 0xfa, 0x1a, 0x66, 0x02, 0x50, 0x0e, 0xc9, 0x4f, 0xd8, 0x02, 0xb2, 0x68, 0xad, 0x24, 0x50, + 0x08, 0x46, 0x7d, 0x0d, 0x42, 0x19, 0x00, 0x66, 0xe8, 0x67, 0xc3, 0xad, 0x10, 0xc0, 0x3e, 0x6b, + 0x7b, 0x5c, 0x56, 0x16, 0xfa, 0x0a, 0xb2, 0x15, 0xaf, 0x6e, 0x0a, 0xcc, 0x66, 0x6d, 0x0e, 0x67, + 0x88, 0xb4, 0x0a, 0xc8, 0x94, 0x46, 0xab, 0x8c, 0x82, 0x69, 0xb4, 0xaa, 0xc0, 0x96, 0x90, 0xc1, + 0x4f, 0xfc, 0xa9, 0x45, 0x9f, 0xc1, 0x39, 0xca, 0x91, 0x90, 0xc1, 0x43, 0xe4, 0xe2, 0x9b, 0xe8, + 0xbf, 0x6a, 0x22, 0xb0, 0x48, 0xc9, 0xbb, 0x5a, 0x78, 0x4a, 0xc9, 0xbb, 0x09, 0x88, 0x53, 0x4c, + 0x3d, 0x75, 0xc2, 0x30, 0xf5, 0x81, 0x1f, 0x1e, 0x8e, 0xe4, 0xe1, 0x59, 0x34, 0x82, 0x62, 0xd4, + 0x2c, 0x2a, 0xc1, 0x3d, 0xd6, 0x5a, 0x32, 0x91, 0x60, 0x74, 0xc9, 0x9b, 0x63, 0x65, 0xb4, 0x41, + 0xf2, 0xb7, 0x95, 0x30, 0x52, 0x59, 0x8f, 0xc7, 0xe0, 0x62, 0xa9, 0x30, 0x36, 0xad, 0x29, 0xa9, + 0x50, 0x37, 0x17, 0x2a, 0xa9, 0x50, 0x3b, 0xf0, 0xa1, 0x37, 0x50, 0x90, 0xb1, 0x17, 0x74, 0x3f, + 0x2e, 0x25, 0x82, 0x4c, 0xd6, 0xc6, 0x50, 0x3a, 0x0b, 0x5f, 0x05, 0xce, 0x41, 0xc3, 0x64, 0x48, + 0x3c, 0x7c, 0x35, 0x68, 0x50, 0xe4, 0x04, 0x01, 0x51, 0xd1, 0x3a, 0x41, 0x46, 0x8b, 0xb4, 0x4e, + 0x50, 0x00, 0x20, 0x3f, 0xdc, 0xe2, 0x68, 0x0d, 0x1a, 0x21, 0xa9, 0xe9, 0x22, 0xf4, 0x90, 0x4f, + 0xe8, 0x90, 0x68, 0x80, 0xd4, 0x39, 0x44, 0x9a, 0x63, 0x75, 0x0e, 0x91, 0xe7, 0xcf, 0x50, 0x6b, + 0x34, 0x16, 0xea, 0xb4, 0x4a, 0xd3, 0xa9, 0x4e, 0xab, 0x3c, 0x55, 0xa2, 0x77, 0x50, 0x54, 0xa7, + 0x3d, 0xa4, 0x91, 0x52, 0x1a, 0x84, 0x07, 0x23, 0x38, 0x08, 0x3e, 0xf8, 0xe9, 0x87, 0xc7, 0xd7, + 0xae, 0x77, 0xdb, 0xba, 0xd8, 0x79, 0xfa, 0x74, 0xe7, 0xb2, 0xbb, 0x7b, 0xf9, 0x6d, 0xcb, 0xdb, + 0xa5, 0x52, 0xbb, 0xc4, 0xe9, 0x7f, 0x74, 0x2f, 0x9d, 0x5d, 0x2a, 0x7d, 0x91, 0xa5, 0xff, 0x44, + 0xf8, 0xf4, 0x7f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x93, 0x5c, 0x42, 0x76, 0x53, 0x28, 0x00, 0x00, +} diff --git a/service/group/api/group.proto b/service/group/api/group.proto new file mode 100644 index 0000000..6bbf74d --- /dev/null +++ b/service/group/api/group.proto @@ -0,0 +1,519 @@ +// protoc -I=. -I=$GOPATH/src --go_out=plugins=grpc:. *.proto +syntax = "proto3"; + +package dtalk.group; +option go_package = "gitlab.33.cn/chat/dtalk/service/group"; + +// 群状态 +enum GroupStatus { + GROUP_STATUS_NORMAL = 0; // 正常 + GROUP_STATUS_BLOCK = 1; // 封禁 + GROUP_STATUS_DISBAND = 2 ;// 解散 +} + +// 群类型 +enum GroupType { + GROUP_TYPE_NORMAL = 0; // 普通群 + GROUP_TYPE_ENT = 1; // 全员群 + GROUP_TYPE_DEP = 2; // 部门群 +} + +// 群内加群设置 +enum GroupJoinType { + GROUP_JOIN_TYPE_ANY = 0; // 无需审批(默认) + GROUP_JOIN_TYPE_ADMIN = 1; // 禁止加群,群主和管理员邀请加群 + GROUP_JOIN_TYPE_APPLY = 2; // 普通人邀请需要审批,群主和管理员直接加群 +} + +// 群内发言设置 +enum GroupMuteType { + GROUP_MUTE_TYPE_ANY = 0; // 全员可发言 + GROUP_MUTE_TYPE_ADMIN = 1; // 全员禁言(除群主和管理员) +} + +// 群内加好友设置 +enum GroupFriendType { + GROUP_FRIEND_TYPE_ALLOW = 0; // 群内可加好友 + GROUP_FRIEND_TYPE_DENY = 1; // 群内禁止加好友 +} + +// 群成员类型 +enum GroupMemberType { + GROUP_MEMBER_TYPE_NORMAL = 0; // 群员 + GROUP_MEMBER_TYPE_ADMIN = 1; // 管理员 + GROUP_MEMBER_TYPE_OWNER = 2; // 群主 + GROUP_MEMBER_TYPE_OTHER = 10; // 退群 +} + +message GroupInfo { + int64 id = 1; + string name = 2; + string avatar = 3; +} + +message GroupMemberInfo { + // 成员 id + string id = 1; + // 成员在群里的名字 + string name = 2; +} + +message GetGroupIdsRequest{ + string member_id = 1; +} + +message GetGroupIdsReply { + repeated int64 group_ids = 1; +} + +message CheckInGroupRequest{ + string member_id = 1; + int64 group_id = 2; +} + +message CheckInGroupReply { + bool is_ok = 1; +} + +message GetMemberIdsRequest{ + int64 group_id = 1; +} + +message GetMemberIdsReply{ + repeated string member_ids = 1; +} + +message CheckMuteRequest { + string member_id = 1; + int64 group_id = 2; +} + +message CheckMuteReply { + bool is_ok = 1; +} + +// 得到加入的所有群 +message GetGroupsReq { + string id = 1; +} + +message GetGroupsResp { + repeated GroupInfo groups = 1; +} + +// ---------------- ---------------------- + +message GroupBizInfo { + int64 id = 1; + string mark_id = 2; + string name = 3; + string avatar = 4; + int32 member_num = 5; + int32 member_maximum = 6; + string introduce = 7; + // 群状态,0=正常 1=封禁 2=解散 + GroupStatus status = 8; + string owner_id = 9; + int64 create_time = 10; + int64 update_time = 11; + // 加群方式,0=无需审批(默认),1=禁止加群,群主和管理员邀请加群, 2=普通人邀请需要审批,群主和管理员直接加群 + GroupJoinType join_type = 12; + // 禁言, 0=全员可发言, 1=全员禁言(除群主和管理员) + GroupMuteType mute_type = 13; + // 加好友限制, 0=群内可加好友,1=群内禁止加好友 + GroupFriendType friend_type = 14; + // 群内当前被禁言的人数 + int32 mute_num = 15; + // 群内管理员数量 + int32 admin_num = 16; + string AES_key = 17; + string pub_name = 18; + // 群类型 (0: 普通群, 1: 全员群, 2: 部门群) + GroupType type = 19; + GroupMemberBizInfo owner = 20; + GroupMemberBizInfo person = 21; + repeated GroupMemberBizInfo members = 22; +} + +message GroupMemberBizInfo { + int64 group_id = 1; + string id = 2; + string name = 3; + // 用户角色,0=群员,1=管理员, 2=群主,10=退群 + GroupMemberType type = 4; + // 该用户被禁言结束的时间 9223372036854775807=永久禁言 + int64 mute_time = 5; + int64 join_time = 6; +} + +// 创建群聊 +message CreateGroupReq { + string name = 1; + GroupType group_type = 2; + GroupMemberInfo owner = 3; + repeated GroupMemberInfo members = 4; +} + +message CreateGroupResp { + int64 group_id = 1; +} + +message InviteGroupMembersReq { + int64 group_id = 1; + string inviter_id = 2; + repeated string member_ids = 3; +} + +message InviteGroupMembersResp { +} + +message GroupExitReq { + int64 group_id = 1; + string person_id = 2; +} + +message GroupExitResp { +} + +message GroupDisbandReq { + int64 group_id = 1; + string person_id = 2; +} + +message GroupDisbandResp { +} + +message GroupRemoveReq { + int64 group_id = 1; + string person_id = 2; + repeated string member_ids = 3; +} + +message GroupRemoveResp { + // 群当前人数 + int32 member_num = 1; + // 成功被踢的成员列表 + repeated string member_ids = 2; +} + +message ChangeOwnerReq { + int64 group_id = 1; + string person_id = 2; + // 被转让为群主的群成员 id + string member_id = 3; +} + +message ChangeOwnerResp { +} + +message UpdateGroupNameReq { + int64 group_id = 1; + string person_id = 2; + string name = 3; + string public_name = 4; +} + +message UpdateGroupNameResp { +} + +message UpdateGroupAvatarReq { + int64 group_id = 1; + string person_id = 2; + // 群头像 url + string avatar = 3; +} + +message UpdateGroupAvatarResp { +} + +message UpdateGroupJoinTypeReq { + int64 group_id = 1; + string person_id = 2; + GroupJoinType group_join_type = 3; +} + +message UpdateGroupJoinTypeResp { +} + +message UpdateGroupFriendTypeReq { + int64 group_id = 1; + string person_id = 2; + GroupFriendType group_friend_type = 3; +} + +message UpdateGroupFriendTypeResp { +} + +message UpdateGroupMuteTypeReq { + int64 group_id = 1; + string person_id = 2; + GroupMuteType group_mute_type = 3; +} + +message UpdateGroupMuteTypeResp { +} + +message UpdateGroupMemberNameReq { + int64 group_id = 1; + string person_id = 2; + string member_name = 3; +} + +message UpdateGroupMemberNameResp { +} + +message SetAdminReq { + int64 group_id = 1; + string person_id = 2; + string member_id = 3; + GroupMemberType group_member_type = 4; +} + +message SetAdminResp { +} + +message UpdateGroupMemberMuteTimeReq { + int64 group_id = 1; + string person_id = 2; + repeated string member_ids = 3; + int64 mute_time = 4; +} + +message UpdateGroupMemberMuteTimeResp { + repeated GroupMemberBizInfo members = 1; +} + +message GetPriGroupInfoReq { + int64 group_id = 1; + string person_id = 2; + int32 display_num = 3; +} + +message GetPriGroupInfoResp { + GroupBizInfo group = 1; +} + +message GetPubGroupInfoReq { + int64 group_id = 1; + string person_id = 2; +} + +message GetPubGroupInfoResp { + GroupBizInfo group = 1; +} + +message GetGroupListReq { + string person_id = 1; +} + +message GetGroupListResp { + repeated GroupBizInfo groups = 1; +} + +message GetGroupMemberListReq { + int64 group_id = 1; + string person_id = 2; +} + +message GetGroupMemberListResp { + repeated GroupMemberBizInfo members = 1; +} + +message GetGroupMemberInfoReq { + int64 group_id = 1; + string person_id = 2; + string member_id = 3; +} + +message GetGroupMemberInfoResp { + GroupMemberBizInfo member = 1; +} + +message GetMuteListReq { + int64 group_id = 1; + string person_id = 2; +} + +message GetMuteListResp { + repeated GroupMemberBizInfo members = 1; +} + +// -------------- oa 远程调用 ---------------- + +message ForceAddMemberReq{ + string member_id = 1; + int64 group_id = 2; +} + +message ForceAddMemberResp{ +} + +message ForceAddMembersReq { + int64 group_id = 1; + repeated GroupMemberInfo members = 2; +} + +message ForceAddMembersResp{ +} + +message ForceDeleteMemberReq{ + string member_id = 1; + int64 group_id = 2; +} + +message ForceDeleteMemberResp{ +} + +message ForceDeleteMembersReq{ + int64 group_id = 1; + repeated string member_ids = 2; +} + +message ForceDeleteMembersResp{ +} + +message GetMemberReq{ + string member_id = 1; + int64 group_id = 2; +} + +message GetMemberResp{ + int64 groupId = 1; + string groupMemberId = 2; + string groupMemberName = 3; + int32 groupMemberType = 4; + int64 groupMemberJoinTime = 5; + int64 groupMemberUpdateTime = 6; +} + +message GetGroupInfoReq { + int64 groupId = 1; +} + +message GetGroupInfoResp { + int64 groupId = 1; + bool group_exist = 2; + string group_name = 3; + string group_avatar = 4; + string group_owner_id = 5; +} + +message ForceDisbandGroupReq { + int64 group_id = 1; + string ope_id = 2; +} + +message ForceDisbandGroupResp { +} + +message ForceUpdateGroupTypeReq { + int64 group_id = 1; + int32 group_type = 2; +} + +message ForceUpdateGroupTypeResp { + int64 group_id = 1; + int32 group_type = 2; +} + +message ForceJoinGroupsReq { + GroupMemberInfo member = 1; + repeated int64 group_ids = 2; +} + +message ForceJoinGroupsResp { +} + +message ForceExitGroupsReq { + GroupMemberInfo member = 1; + repeated int64 group_ids = 2; +} + +message ForceExitGroupsResp { +} + +message ForceChangeOwnerReq { + int64 group_id = 1; + GroupMemberInfo member = 2; +} + +message ForceChangeOwnerResp {} + +service Group { + rpc GetGroupIds(GetGroupIdsRequest) returns (GetGroupIdsReply); + rpc CheckInGroup(CheckInGroupRequest) returns (CheckInGroupReply); + rpc GetMemberIds(GetMemberIdsRequest) returns (GetMemberIdsReply); + rpc CheckMute(CheckMuteRequest) returns (CheckMuteReply); + + // 得到加入的所有群 + rpc GetGroups(GetGroupsReq) returns (GetGroupsResp); + // 查询成员 + rpc GetMember(GetMemberReq) returns (GetMemberResp); + // 查询群信息 + rpc GetGroupInfo(GetGroupInfoReq) returns (GetGroupInfoResp); + + // ------------- gateway ----------- + + // 创建群聊 + rpc CreateGroup(CreateGroupReq) returns (CreateGroupResp); + // 邀请新成员 + rpc InviteGroupMembers(InviteGroupMembersReq) returns (InviteGroupMembersResp); + // 退出群 + rpc GroupExit(GroupExitReq) returns (GroupExitResp); + // 解散群 + rpc GroupDisband(GroupDisbandReq) returns (GroupDisbandResp); + // 踢人 + rpc GroupRemove(GroupRemoveReq) returns (GroupRemoveResp); + // 转让群主 + rpc ChangeOwner(ChangeOwnerReq) returns (ChangeOwnerResp); + // 更新群名称 + rpc UpdateGroupName(UpdateGroupNameReq) returns(UpdateGroupNameResp); + // 更新群头像 + rpc UpdateGroupAvatar(UpdateGroupAvatarReq) returns (UpdateGroupAvatarResp); + // 更新加群设置 + rpc UpdateGroupJoinType(UpdateGroupJoinTypeReq) returns (UpdateGroupJoinTypeResp); + // 更新群内加好友设置 + rpc UpdateGroupFriendType(UpdateGroupFriendTypeReq) returns (UpdateGroupFriendTypeResp); + // 更新群内禁言设置 + rpc UpdateGroupMuteType(UpdateGroupMuteTypeReq) returns (UpdateGroupMuteTypeResp); + // 更新群内昵称 + rpc UpdateGroupMemberName(UpdateGroupMemberNameReq) returns (UpdateGroupMemberNameResp); + // 设置群管理员 + rpc SetAdmin(SetAdminReq) returns(SetAdminResp); + // 设置群成员禁言时间 + rpc UpdateGroupMemberMuteTime(UpdateGroupMemberMuteTimeReq) returns(UpdateGroupMemberMuteTimeResp); + // 查询完整群信息 + rpc GetPriGroupInfo(GetPriGroupInfoReq) returns(GetPriGroupInfoResp); + // 查询群公开信息 + rpc GetPubGroupInfo(GetPubGroupInfoReq) returns (GetPubGroupInfoResp); + // 查询加入的群列表 + rpc GetGroupList(GetGroupListReq) returns (GetGroupListResp); + // 查询群成员列表 + rpc GetGroupMemberList(GetGroupMemberListReq) returns (GetGroupMemberListResp); + // 查询群成员信息 + rpc GetGroupMemberInfo(GetGroupMemberInfoReq) returns (GetGroupMemberInfoResp); + // 查询群内禁言列表 + rpc GetMuteList(GetMuteListReq) returns (GetMuteListResp); + + + // ------------- Third Force -------------- + + // 更新群类型 + rpc ForceUpdateGroupType(ForceUpdateGroupTypeReq) returns(ForceUpdateGroupTypeResp); + // 解散群, 强制解散 + rpc ForceDisbandGroup(ForceDisbandGroupReq) returns (ForceDisbandGroupResp); + // 添加成员 + rpc ForceAddMember(ForceAddMemberReq) returns (ForceAddMemberResp); + // 批量添加成员 + rpc ForceAddMembers(ForceAddMembersReq) returns (ForceAddMembersResp); + // 删除成员 + rpc ForceDeleteMember(ForceDeleteMemberReq) returns (ForceDeleteMemberResp); + // 批量删除成员 + rpc ForceDeleteMembers(ForceDeleteMembersReq) returns (ForceDeleteMembersResp); + // 一个人加入多个群 + rpc ForceJoinGroups(ForceJoinGroupsReq) returns(ForceJoinGroupsResp); + // 一个人退出多个群 + rpc ForceExitGroups(ForceExitGroupsReq) returns(ForceExitGroupsResp); + // 成为群主 + rpc ForceChangeOwner(ForceChangeOwnerReq) returns(ForceChangeOwnerResp); +} diff --git a/service/group/api/group_grpc.pb.go b/service/group/api/group_grpc.pb.go new file mode 100644 index 0000000..f0bb3f6 --- /dev/null +++ b/service/group/api/group_grpc.pb.go @@ -0,0 +1,1425 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. + +package group + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// GroupClient is the client API for Group service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type GroupClient interface { + GetGroupIds(ctx context.Context, in *GetGroupIdsRequest, opts ...grpc.CallOption) (*GetGroupIdsReply, error) + CheckInGroup(ctx context.Context, in *CheckInGroupRequest, opts ...grpc.CallOption) (*CheckInGroupReply, error) + GetMemberIds(ctx context.Context, in *GetMemberIdsRequest, opts ...grpc.CallOption) (*GetMemberIdsReply, error) + CheckMute(ctx context.Context, in *CheckMuteRequest, opts ...grpc.CallOption) (*CheckMuteReply, error) + // 得到加入的所有群 + GetGroups(ctx context.Context, in *GetGroupsReq, opts ...grpc.CallOption) (*GetGroupsResp, error) + // 查询成员 + GetMember(ctx context.Context, in *GetMemberReq, opts ...grpc.CallOption) (*GetMemberResp, error) + // 查询群信息 + GetGroupInfo(ctx context.Context, in *GetGroupInfoReq, opts ...grpc.CallOption) (*GetGroupInfoResp, error) + // 创建群聊 + CreateGroup(ctx context.Context, in *CreateGroupReq, opts ...grpc.CallOption) (*CreateGroupResp, error) + // 邀请新成员 + InviteGroupMembers(ctx context.Context, in *InviteGroupMembersReq, opts ...grpc.CallOption) (*InviteGroupMembersResp, error) + // 退出群 + GroupExit(ctx context.Context, in *GroupExitReq, opts ...grpc.CallOption) (*GroupExitResp, error) + // 解散群 + GroupDisband(ctx context.Context, in *GroupDisbandReq, opts ...grpc.CallOption) (*GroupDisbandResp, error) + // 踢人 + GroupRemove(ctx context.Context, in *GroupRemoveReq, opts ...grpc.CallOption) (*GroupRemoveResp, error) + // 转让群主 + ChangeOwner(ctx context.Context, in *ChangeOwnerReq, opts ...grpc.CallOption) (*ChangeOwnerResp, error) + // 更新群名称 + UpdateGroupName(ctx context.Context, in *UpdateGroupNameReq, opts ...grpc.CallOption) (*UpdateGroupNameResp, error) + // 更新群头像 + UpdateGroupAvatar(ctx context.Context, in *UpdateGroupAvatarReq, opts ...grpc.CallOption) (*UpdateGroupAvatarResp, error) + // 更新加群设置 + UpdateGroupJoinType(ctx context.Context, in *UpdateGroupJoinTypeReq, opts ...grpc.CallOption) (*UpdateGroupJoinTypeResp, error) + // 更新群内加好友设置 + UpdateGroupFriendType(ctx context.Context, in *UpdateGroupFriendTypeReq, opts ...grpc.CallOption) (*UpdateGroupFriendTypeResp, error) + // 更新群内禁言设置 + UpdateGroupMuteType(ctx context.Context, in *UpdateGroupMuteTypeReq, opts ...grpc.CallOption) (*UpdateGroupMuteTypeResp, error) + // 更新群内昵称 + UpdateGroupMemberName(ctx context.Context, in *UpdateGroupMemberNameReq, opts ...grpc.CallOption) (*UpdateGroupMemberNameResp, error) + // 设置群管理员 + SetAdmin(ctx context.Context, in *SetAdminReq, opts ...grpc.CallOption) (*SetAdminResp, error) + // 设置群成员禁言时间 + UpdateGroupMemberMuteTime(ctx context.Context, in *UpdateGroupMemberMuteTimeReq, opts ...grpc.CallOption) (*UpdateGroupMemberMuteTimeResp, error) + // 查询完整群信息 + GetPriGroupInfo(ctx context.Context, in *GetPriGroupInfoReq, opts ...grpc.CallOption) (*GetPriGroupInfoResp, error) + // 查询群公开信息 + GetPubGroupInfo(ctx context.Context, in *GetPubGroupInfoReq, opts ...grpc.CallOption) (*GetPubGroupInfoResp, error) + // 查询加入的群列表 + GetGroupList(ctx context.Context, in *GetGroupListReq, opts ...grpc.CallOption) (*GetGroupListResp, error) + // 查询群成员列表 + GetGroupMemberList(ctx context.Context, in *GetGroupMemberListReq, opts ...grpc.CallOption) (*GetGroupMemberListResp, error) + // 查询群成员信息 + GetGroupMemberInfo(ctx context.Context, in *GetGroupMemberInfoReq, opts ...grpc.CallOption) (*GetGroupMemberInfoResp, error) + // 查询群内禁言列表 + GetMuteList(ctx context.Context, in *GetMuteListReq, opts ...grpc.CallOption) (*GetMuteListResp, error) + // 更新群类型 + ForceUpdateGroupType(ctx context.Context, in *ForceUpdateGroupTypeReq, opts ...grpc.CallOption) (*ForceUpdateGroupTypeResp, error) + // 解散群, 强制解散 + ForceDisbandGroup(ctx context.Context, in *ForceDisbandGroupReq, opts ...grpc.CallOption) (*ForceDisbandGroupResp, error) + // 添加成员 + ForceAddMember(ctx context.Context, in *ForceAddMemberReq, opts ...grpc.CallOption) (*ForceAddMemberResp, error) + // 批量添加成员 + ForceAddMembers(ctx context.Context, in *ForceAddMembersReq, opts ...grpc.CallOption) (*ForceAddMembersResp, error) + // 删除成员 + ForceDeleteMember(ctx context.Context, in *ForceDeleteMemberReq, opts ...grpc.CallOption) (*ForceDeleteMemberResp, error) + // 批量删除成员 + ForceDeleteMembers(ctx context.Context, in *ForceDeleteMembersReq, opts ...grpc.CallOption) (*ForceDeleteMembersResp, error) + // 一个人加入多个群 + ForceJoinGroups(ctx context.Context, in *ForceJoinGroupsReq, opts ...grpc.CallOption) (*ForceJoinGroupsResp, error) + // 一个人退出多个群 + ForceExitGroups(ctx context.Context, in *ForceExitGroupsReq, opts ...grpc.CallOption) (*ForceExitGroupsResp, error) + // 成为群主 + ForceChangeOwner(ctx context.Context, in *ForceChangeOwnerReq, opts ...grpc.CallOption) (*ForceChangeOwnerResp, error) +} + +type groupClient struct { + cc grpc.ClientConnInterface +} + +func NewGroupClient(cc grpc.ClientConnInterface) GroupClient { + return &groupClient{cc} +} + +func (c *groupClient) GetGroupIds(ctx context.Context, in *GetGroupIdsRequest, opts ...grpc.CallOption) (*GetGroupIdsReply, error) { + out := new(GetGroupIdsReply) + err := c.cc.Invoke(ctx, "/dtalk.group.Group/GetGroupIds", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *groupClient) CheckInGroup(ctx context.Context, in *CheckInGroupRequest, opts ...grpc.CallOption) (*CheckInGroupReply, error) { + out := new(CheckInGroupReply) + err := c.cc.Invoke(ctx, "/dtalk.group.Group/CheckInGroup", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *groupClient) GetMemberIds(ctx context.Context, in *GetMemberIdsRequest, opts ...grpc.CallOption) (*GetMemberIdsReply, error) { + out := new(GetMemberIdsReply) + err := c.cc.Invoke(ctx, "/dtalk.group.Group/GetMemberIds", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *groupClient) CheckMute(ctx context.Context, in *CheckMuteRequest, opts ...grpc.CallOption) (*CheckMuteReply, error) { + out := new(CheckMuteReply) + err := c.cc.Invoke(ctx, "/dtalk.group.Group/CheckMute", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *groupClient) GetGroups(ctx context.Context, in *GetGroupsReq, opts ...grpc.CallOption) (*GetGroupsResp, error) { + out := new(GetGroupsResp) + err := c.cc.Invoke(ctx, "/dtalk.group.Group/GetGroups", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *groupClient) GetMember(ctx context.Context, in *GetMemberReq, opts ...grpc.CallOption) (*GetMemberResp, error) { + out := new(GetMemberResp) + err := c.cc.Invoke(ctx, "/dtalk.group.Group/GetMember", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *groupClient) GetGroupInfo(ctx context.Context, in *GetGroupInfoReq, opts ...grpc.CallOption) (*GetGroupInfoResp, error) { + out := new(GetGroupInfoResp) + err := c.cc.Invoke(ctx, "/dtalk.group.Group/GetGroupInfo", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *groupClient) CreateGroup(ctx context.Context, in *CreateGroupReq, opts ...grpc.CallOption) (*CreateGroupResp, error) { + out := new(CreateGroupResp) + err := c.cc.Invoke(ctx, "/dtalk.group.Group/CreateGroup", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *groupClient) InviteGroupMembers(ctx context.Context, in *InviteGroupMembersReq, opts ...grpc.CallOption) (*InviteGroupMembersResp, error) { + out := new(InviteGroupMembersResp) + err := c.cc.Invoke(ctx, "/dtalk.group.Group/InviteGroupMembers", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *groupClient) GroupExit(ctx context.Context, in *GroupExitReq, opts ...grpc.CallOption) (*GroupExitResp, error) { + out := new(GroupExitResp) + err := c.cc.Invoke(ctx, "/dtalk.group.Group/GroupExit", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *groupClient) GroupDisband(ctx context.Context, in *GroupDisbandReq, opts ...grpc.CallOption) (*GroupDisbandResp, error) { + out := new(GroupDisbandResp) + err := c.cc.Invoke(ctx, "/dtalk.group.Group/GroupDisband", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *groupClient) GroupRemove(ctx context.Context, in *GroupRemoveReq, opts ...grpc.CallOption) (*GroupRemoveResp, error) { + out := new(GroupRemoveResp) + err := c.cc.Invoke(ctx, "/dtalk.group.Group/GroupRemove", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *groupClient) ChangeOwner(ctx context.Context, in *ChangeOwnerReq, opts ...grpc.CallOption) (*ChangeOwnerResp, error) { + out := new(ChangeOwnerResp) + err := c.cc.Invoke(ctx, "/dtalk.group.Group/ChangeOwner", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *groupClient) UpdateGroupName(ctx context.Context, in *UpdateGroupNameReq, opts ...grpc.CallOption) (*UpdateGroupNameResp, error) { + out := new(UpdateGroupNameResp) + err := c.cc.Invoke(ctx, "/dtalk.group.Group/UpdateGroupName", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *groupClient) UpdateGroupAvatar(ctx context.Context, in *UpdateGroupAvatarReq, opts ...grpc.CallOption) (*UpdateGroupAvatarResp, error) { + out := new(UpdateGroupAvatarResp) + err := c.cc.Invoke(ctx, "/dtalk.group.Group/UpdateGroupAvatar", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *groupClient) UpdateGroupJoinType(ctx context.Context, in *UpdateGroupJoinTypeReq, opts ...grpc.CallOption) (*UpdateGroupJoinTypeResp, error) { + out := new(UpdateGroupJoinTypeResp) + err := c.cc.Invoke(ctx, "/dtalk.group.Group/UpdateGroupJoinType", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *groupClient) UpdateGroupFriendType(ctx context.Context, in *UpdateGroupFriendTypeReq, opts ...grpc.CallOption) (*UpdateGroupFriendTypeResp, error) { + out := new(UpdateGroupFriendTypeResp) + err := c.cc.Invoke(ctx, "/dtalk.group.Group/UpdateGroupFriendType", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *groupClient) UpdateGroupMuteType(ctx context.Context, in *UpdateGroupMuteTypeReq, opts ...grpc.CallOption) (*UpdateGroupMuteTypeResp, error) { + out := new(UpdateGroupMuteTypeResp) + err := c.cc.Invoke(ctx, "/dtalk.group.Group/UpdateGroupMuteType", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *groupClient) UpdateGroupMemberName(ctx context.Context, in *UpdateGroupMemberNameReq, opts ...grpc.CallOption) (*UpdateGroupMemberNameResp, error) { + out := new(UpdateGroupMemberNameResp) + err := c.cc.Invoke(ctx, "/dtalk.group.Group/UpdateGroupMemberName", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *groupClient) SetAdmin(ctx context.Context, in *SetAdminReq, opts ...grpc.CallOption) (*SetAdminResp, error) { + out := new(SetAdminResp) + err := c.cc.Invoke(ctx, "/dtalk.group.Group/SetAdmin", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *groupClient) UpdateGroupMemberMuteTime(ctx context.Context, in *UpdateGroupMemberMuteTimeReq, opts ...grpc.CallOption) (*UpdateGroupMemberMuteTimeResp, error) { + out := new(UpdateGroupMemberMuteTimeResp) + err := c.cc.Invoke(ctx, "/dtalk.group.Group/UpdateGroupMemberMuteTime", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *groupClient) GetPriGroupInfo(ctx context.Context, in *GetPriGroupInfoReq, opts ...grpc.CallOption) (*GetPriGroupInfoResp, error) { + out := new(GetPriGroupInfoResp) + err := c.cc.Invoke(ctx, "/dtalk.group.Group/GetPriGroupInfo", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *groupClient) GetPubGroupInfo(ctx context.Context, in *GetPubGroupInfoReq, opts ...grpc.CallOption) (*GetPubGroupInfoResp, error) { + out := new(GetPubGroupInfoResp) + err := c.cc.Invoke(ctx, "/dtalk.group.Group/GetPubGroupInfo", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *groupClient) GetGroupList(ctx context.Context, in *GetGroupListReq, opts ...grpc.CallOption) (*GetGroupListResp, error) { + out := new(GetGroupListResp) + err := c.cc.Invoke(ctx, "/dtalk.group.Group/GetGroupList", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *groupClient) GetGroupMemberList(ctx context.Context, in *GetGroupMemberListReq, opts ...grpc.CallOption) (*GetGroupMemberListResp, error) { + out := new(GetGroupMemberListResp) + err := c.cc.Invoke(ctx, "/dtalk.group.Group/GetGroupMemberList", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *groupClient) GetGroupMemberInfo(ctx context.Context, in *GetGroupMemberInfoReq, opts ...grpc.CallOption) (*GetGroupMemberInfoResp, error) { + out := new(GetGroupMemberInfoResp) + err := c.cc.Invoke(ctx, "/dtalk.group.Group/GetGroupMemberInfo", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *groupClient) GetMuteList(ctx context.Context, in *GetMuteListReq, opts ...grpc.CallOption) (*GetMuteListResp, error) { + out := new(GetMuteListResp) + err := c.cc.Invoke(ctx, "/dtalk.group.Group/GetMuteList", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *groupClient) ForceUpdateGroupType(ctx context.Context, in *ForceUpdateGroupTypeReq, opts ...grpc.CallOption) (*ForceUpdateGroupTypeResp, error) { + out := new(ForceUpdateGroupTypeResp) + err := c.cc.Invoke(ctx, "/dtalk.group.Group/ForceUpdateGroupType", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *groupClient) ForceDisbandGroup(ctx context.Context, in *ForceDisbandGroupReq, opts ...grpc.CallOption) (*ForceDisbandGroupResp, error) { + out := new(ForceDisbandGroupResp) + err := c.cc.Invoke(ctx, "/dtalk.group.Group/ForceDisbandGroup", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *groupClient) ForceAddMember(ctx context.Context, in *ForceAddMemberReq, opts ...grpc.CallOption) (*ForceAddMemberResp, error) { + out := new(ForceAddMemberResp) + err := c.cc.Invoke(ctx, "/dtalk.group.Group/ForceAddMember", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *groupClient) ForceAddMembers(ctx context.Context, in *ForceAddMembersReq, opts ...grpc.CallOption) (*ForceAddMembersResp, error) { + out := new(ForceAddMembersResp) + err := c.cc.Invoke(ctx, "/dtalk.group.Group/ForceAddMembers", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *groupClient) ForceDeleteMember(ctx context.Context, in *ForceDeleteMemberReq, opts ...grpc.CallOption) (*ForceDeleteMemberResp, error) { + out := new(ForceDeleteMemberResp) + err := c.cc.Invoke(ctx, "/dtalk.group.Group/ForceDeleteMember", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *groupClient) ForceDeleteMembers(ctx context.Context, in *ForceDeleteMembersReq, opts ...grpc.CallOption) (*ForceDeleteMembersResp, error) { + out := new(ForceDeleteMembersResp) + err := c.cc.Invoke(ctx, "/dtalk.group.Group/ForceDeleteMembers", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *groupClient) ForceJoinGroups(ctx context.Context, in *ForceJoinGroupsReq, opts ...grpc.CallOption) (*ForceJoinGroupsResp, error) { + out := new(ForceJoinGroupsResp) + err := c.cc.Invoke(ctx, "/dtalk.group.Group/ForceJoinGroups", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *groupClient) ForceExitGroups(ctx context.Context, in *ForceExitGroupsReq, opts ...grpc.CallOption) (*ForceExitGroupsResp, error) { + out := new(ForceExitGroupsResp) + err := c.cc.Invoke(ctx, "/dtalk.group.Group/ForceExitGroups", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *groupClient) ForceChangeOwner(ctx context.Context, in *ForceChangeOwnerReq, opts ...grpc.CallOption) (*ForceChangeOwnerResp, error) { + out := new(ForceChangeOwnerResp) + err := c.cc.Invoke(ctx, "/dtalk.group.Group/ForceChangeOwner", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// GroupServer is the server API for Group service. +// All implementations must embed UnimplementedGroupServer +// for forward compatibility +type GroupServer interface { + GetGroupIds(context.Context, *GetGroupIdsRequest) (*GetGroupIdsReply, error) + CheckInGroup(context.Context, *CheckInGroupRequest) (*CheckInGroupReply, error) + GetMemberIds(context.Context, *GetMemberIdsRequest) (*GetMemberIdsReply, error) + CheckMute(context.Context, *CheckMuteRequest) (*CheckMuteReply, error) + // 得到加入的所有群 + GetGroups(context.Context, *GetGroupsReq) (*GetGroupsResp, error) + // 查询成员 + GetMember(context.Context, *GetMemberReq) (*GetMemberResp, error) + // 查询群信息 + GetGroupInfo(context.Context, *GetGroupInfoReq) (*GetGroupInfoResp, error) + // 创建群聊 + CreateGroup(context.Context, *CreateGroupReq) (*CreateGroupResp, error) + // 邀请新成员 + InviteGroupMembers(context.Context, *InviteGroupMembersReq) (*InviteGroupMembersResp, error) + // 退出群 + GroupExit(context.Context, *GroupExitReq) (*GroupExitResp, error) + // 解散群 + GroupDisband(context.Context, *GroupDisbandReq) (*GroupDisbandResp, error) + // 踢人 + GroupRemove(context.Context, *GroupRemoveReq) (*GroupRemoveResp, error) + // 转让群主 + ChangeOwner(context.Context, *ChangeOwnerReq) (*ChangeOwnerResp, error) + // 更新群名称 + UpdateGroupName(context.Context, *UpdateGroupNameReq) (*UpdateGroupNameResp, error) + // 更新群头像 + UpdateGroupAvatar(context.Context, *UpdateGroupAvatarReq) (*UpdateGroupAvatarResp, error) + // 更新加群设置 + UpdateGroupJoinType(context.Context, *UpdateGroupJoinTypeReq) (*UpdateGroupJoinTypeResp, error) + // 更新群内加好友设置 + UpdateGroupFriendType(context.Context, *UpdateGroupFriendTypeReq) (*UpdateGroupFriendTypeResp, error) + // 更新群内禁言设置 + UpdateGroupMuteType(context.Context, *UpdateGroupMuteTypeReq) (*UpdateGroupMuteTypeResp, error) + // 更新群内昵称 + UpdateGroupMemberName(context.Context, *UpdateGroupMemberNameReq) (*UpdateGroupMemberNameResp, error) + // 设置群管理员 + SetAdmin(context.Context, *SetAdminReq) (*SetAdminResp, error) + // 设置群成员禁言时间 + UpdateGroupMemberMuteTime(context.Context, *UpdateGroupMemberMuteTimeReq) (*UpdateGroupMemberMuteTimeResp, error) + // 查询完整群信息 + GetPriGroupInfo(context.Context, *GetPriGroupInfoReq) (*GetPriGroupInfoResp, error) + // 查询群公开信息 + GetPubGroupInfo(context.Context, *GetPubGroupInfoReq) (*GetPubGroupInfoResp, error) + // 查询加入的群列表 + GetGroupList(context.Context, *GetGroupListReq) (*GetGroupListResp, error) + // 查询群成员列表 + GetGroupMemberList(context.Context, *GetGroupMemberListReq) (*GetGroupMemberListResp, error) + // 查询群成员信息 + GetGroupMemberInfo(context.Context, *GetGroupMemberInfoReq) (*GetGroupMemberInfoResp, error) + // 查询群内禁言列表 + GetMuteList(context.Context, *GetMuteListReq) (*GetMuteListResp, error) + // 更新群类型 + ForceUpdateGroupType(context.Context, *ForceUpdateGroupTypeReq) (*ForceUpdateGroupTypeResp, error) + // 解散群, 强制解散 + ForceDisbandGroup(context.Context, *ForceDisbandGroupReq) (*ForceDisbandGroupResp, error) + // 添加成员 + ForceAddMember(context.Context, *ForceAddMemberReq) (*ForceAddMemberResp, error) + // 批量添加成员 + ForceAddMembers(context.Context, *ForceAddMembersReq) (*ForceAddMembersResp, error) + // 删除成员 + ForceDeleteMember(context.Context, *ForceDeleteMemberReq) (*ForceDeleteMemberResp, error) + // 批量删除成员 + ForceDeleteMembers(context.Context, *ForceDeleteMembersReq) (*ForceDeleteMembersResp, error) + // 一个人加入多个群 + ForceJoinGroups(context.Context, *ForceJoinGroupsReq) (*ForceJoinGroupsResp, error) + // 一个人退出多个群 + ForceExitGroups(context.Context, *ForceExitGroupsReq) (*ForceExitGroupsResp, error) + // 成为群主 + ForceChangeOwner(context.Context, *ForceChangeOwnerReq) (*ForceChangeOwnerResp, error) + mustEmbedUnimplementedGroupServer() +} + +// UnimplementedGroupServer must be embedded to have forward compatible implementations. +type UnimplementedGroupServer struct { +} + +func (UnimplementedGroupServer) GetGroupIds(context.Context, *GetGroupIdsRequest) (*GetGroupIdsReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetGroupIds not implemented") +} +func (UnimplementedGroupServer) CheckInGroup(context.Context, *CheckInGroupRequest) (*CheckInGroupReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method CheckInGroup not implemented") +} +func (UnimplementedGroupServer) GetMemberIds(context.Context, *GetMemberIdsRequest) (*GetMemberIdsReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetMemberIds not implemented") +} +func (UnimplementedGroupServer) CheckMute(context.Context, *CheckMuteRequest) (*CheckMuteReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method CheckMute not implemented") +} +func (UnimplementedGroupServer) GetGroups(context.Context, *GetGroupsReq) (*GetGroupsResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetGroups not implemented") +} +func (UnimplementedGroupServer) GetMember(context.Context, *GetMemberReq) (*GetMemberResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetMember not implemented") +} +func (UnimplementedGroupServer) GetGroupInfo(context.Context, *GetGroupInfoReq) (*GetGroupInfoResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetGroupInfo not implemented") +} +func (UnimplementedGroupServer) CreateGroup(context.Context, *CreateGroupReq) (*CreateGroupResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateGroup not implemented") +} +func (UnimplementedGroupServer) InviteGroupMembers(context.Context, *InviteGroupMembersReq) (*InviteGroupMembersResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method InviteGroupMembers not implemented") +} +func (UnimplementedGroupServer) GroupExit(context.Context, *GroupExitReq) (*GroupExitResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method GroupExit not implemented") +} +func (UnimplementedGroupServer) GroupDisband(context.Context, *GroupDisbandReq) (*GroupDisbandResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method GroupDisband not implemented") +} +func (UnimplementedGroupServer) GroupRemove(context.Context, *GroupRemoveReq) (*GroupRemoveResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method GroupRemove not implemented") +} +func (UnimplementedGroupServer) ChangeOwner(context.Context, *ChangeOwnerReq) (*ChangeOwnerResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method ChangeOwner not implemented") +} +func (UnimplementedGroupServer) UpdateGroupName(context.Context, *UpdateGroupNameReq) (*UpdateGroupNameResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateGroupName not implemented") +} +func (UnimplementedGroupServer) UpdateGroupAvatar(context.Context, *UpdateGroupAvatarReq) (*UpdateGroupAvatarResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateGroupAvatar not implemented") +} +func (UnimplementedGroupServer) UpdateGroupJoinType(context.Context, *UpdateGroupJoinTypeReq) (*UpdateGroupJoinTypeResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateGroupJoinType not implemented") +} +func (UnimplementedGroupServer) UpdateGroupFriendType(context.Context, *UpdateGroupFriendTypeReq) (*UpdateGroupFriendTypeResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateGroupFriendType not implemented") +} +func (UnimplementedGroupServer) UpdateGroupMuteType(context.Context, *UpdateGroupMuteTypeReq) (*UpdateGroupMuteTypeResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateGroupMuteType not implemented") +} +func (UnimplementedGroupServer) UpdateGroupMemberName(context.Context, *UpdateGroupMemberNameReq) (*UpdateGroupMemberNameResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateGroupMemberName not implemented") +} +func (UnimplementedGroupServer) SetAdmin(context.Context, *SetAdminReq) (*SetAdminResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method SetAdmin not implemented") +} +func (UnimplementedGroupServer) UpdateGroupMemberMuteTime(context.Context, *UpdateGroupMemberMuteTimeReq) (*UpdateGroupMemberMuteTimeResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateGroupMemberMuteTime not implemented") +} +func (UnimplementedGroupServer) GetPriGroupInfo(context.Context, *GetPriGroupInfoReq) (*GetPriGroupInfoResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetPriGroupInfo not implemented") +} +func (UnimplementedGroupServer) GetPubGroupInfo(context.Context, *GetPubGroupInfoReq) (*GetPubGroupInfoResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetPubGroupInfo not implemented") +} +func (UnimplementedGroupServer) GetGroupList(context.Context, *GetGroupListReq) (*GetGroupListResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetGroupList not implemented") +} +func (UnimplementedGroupServer) GetGroupMemberList(context.Context, *GetGroupMemberListReq) (*GetGroupMemberListResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetGroupMemberList not implemented") +} +func (UnimplementedGroupServer) GetGroupMemberInfo(context.Context, *GetGroupMemberInfoReq) (*GetGroupMemberInfoResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetGroupMemberInfo not implemented") +} +func (UnimplementedGroupServer) GetMuteList(context.Context, *GetMuteListReq) (*GetMuteListResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetMuteList not implemented") +} +func (UnimplementedGroupServer) ForceUpdateGroupType(context.Context, *ForceUpdateGroupTypeReq) (*ForceUpdateGroupTypeResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method ForceUpdateGroupType not implemented") +} +func (UnimplementedGroupServer) ForceDisbandGroup(context.Context, *ForceDisbandGroupReq) (*ForceDisbandGroupResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method ForceDisbandGroup not implemented") +} +func (UnimplementedGroupServer) ForceAddMember(context.Context, *ForceAddMemberReq) (*ForceAddMemberResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method ForceAddMember not implemented") +} +func (UnimplementedGroupServer) ForceAddMembers(context.Context, *ForceAddMembersReq) (*ForceAddMembersResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method ForceAddMembers not implemented") +} +func (UnimplementedGroupServer) ForceDeleteMember(context.Context, *ForceDeleteMemberReq) (*ForceDeleteMemberResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method ForceDeleteMember not implemented") +} +func (UnimplementedGroupServer) ForceDeleteMembers(context.Context, *ForceDeleteMembersReq) (*ForceDeleteMembersResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method ForceDeleteMembers not implemented") +} +func (UnimplementedGroupServer) ForceJoinGroups(context.Context, *ForceJoinGroupsReq) (*ForceJoinGroupsResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method ForceJoinGroups not implemented") +} +func (UnimplementedGroupServer) ForceExitGroups(context.Context, *ForceExitGroupsReq) (*ForceExitGroupsResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method ForceExitGroups not implemented") +} +func (UnimplementedGroupServer) ForceChangeOwner(context.Context, *ForceChangeOwnerReq) (*ForceChangeOwnerResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method ForceChangeOwner not implemented") +} +func (UnimplementedGroupServer) mustEmbedUnimplementedGroupServer() {} + +// UnsafeGroupServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to GroupServer will +// result in compilation errors. +type UnsafeGroupServer interface { + mustEmbedUnimplementedGroupServer() +} + +func RegisterGroupServer(s grpc.ServiceRegistrar, srv GroupServer) { + s.RegisterService(&Group_ServiceDesc, srv) +} + +func _Group_GetGroupIds_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetGroupIdsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GroupServer).GetGroupIds(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.group.Group/GetGroupIds", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GroupServer).GetGroupIds(ctx, req.(*GetGroupIdsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Group_CheckInGroup_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CheckInGroupRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GroupServer).CheckInGroup(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.group.Group/CheckInGroup", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GroupServer).CheckInGroup(ctx, req.(*CheckInGroupRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Group_GetMemberIds_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetMemberIdsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GroupServer).GetMemberIds(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.group.Group/GetMemberIds", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GroupServer).GetMemberIds(ctx, req.(*GetMemberIdsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Group_CheckMute_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CheckMuteRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GroupServer).CheckMute(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.group.Group/CheckMute", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GroupServer).CheckMute(ctx, req.(*CheckMuteRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Group_GetGroups_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetGroupsReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GroupServer).GetGroups(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.group.Group/GetGroups", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GroupServer).GetGroups(ctx, req.(*GetGroupsReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Group_GetMember_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetMemberReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GroupServer).GetMember(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.group.Group/GetMember", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GroupServer).GetMember(ctx, req.(*GetMemberReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Group_GetGroupInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetGroupInfoReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GroupServer).GetGroupInfo(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.group.Group/GetGroupInfo", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GroupServer).GetGroupInfo(ctx, req.(*GetGroupInfoReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Group_CreateGroup_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateGroupReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GroupServer).CreateGroup(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.group.Group/CreateGroup", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GroupServer).CreateGroup(ctx, req.(*CreateGroupReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Group_InviteGroupMembers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(InviteGroupMembersReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GroupServer).InviteGroupMembers(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.group.Group/InviteGroupMembers", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GroupServer).InviteGroupMembers(ctx, req.(*InviteGroupMembersReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Group_GroupExit_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GroupExitReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GroupServer).GroupExit(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.group.Group/GroupExit", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GroupServer).GroupExit(ctx, req.(*GroupExitReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Group_GroupDisband_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GroupDisbandReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GroupServer).GroupDisband(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.group.Group/GroupDisband", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GroupServer).GroupDisband(ctx, req.(*GroupDisbandReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Group_GroupRemove_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GroupRemoveReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GroupServer).GroupRemove(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.group.Group/GroupRemove", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GroupServer).GroupRemove(ctx, req.(*GroupRemoveReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Group_ChangeOwner_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ChangeOwnerReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GroupServer).ChangeOwner(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.group.Group/ChangeOwner", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GroupServer).ChangeOwner(ctx, req.(*ChangeOwnerReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Group_UpdateGroupName_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateGroupNameReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GroupServer).UpdateGroupName(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.group.Group/UpdateGroupName", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GroupServer).UpdateGroupName(ctx, req.(*UpdateGroupNameReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Group_UpdateGroupAvatar_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateGroupAvatarReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GroupServer).UpdateGroupAvatar(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.group.Group/UpdateGroupAvatar", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GroupServer).UpdateGroupAvatar(ctx, req.(*UpdateGroupAvatarReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Group_UpdateGroupJoinType_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateGroupJoinTypeReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GroupServer).UpdateGroupJoinType(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.group.Group/UpdateGroupJoinType", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GroupServer).UpdateGroupJoinType(ctx, req.(*UpdateGroupJoinTypeReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Group_UpdateGroupFriendType_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateGroupFriendTypeReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GroupServer).UpdateGroupFriendType(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.group.Group/UpdateGroupFriendType", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GroupServer).UpdateGroupFriendType(ctx, req.(*UpdateGroupFriendTypeReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Group_UpdateGroupMuteType_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateGroupMuteTypeReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GroupServer).UpdateGroupMuteType(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.group.Group/UpdateGroupMuteType", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GroupServer).UpdateGroupMuteType(ctx, req.(*UpdateGroupMuteTypeReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Group_UpdateGroupMemberName_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateGroupMemberNameReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GroupServer).UpdateGroupMemberName(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.group.Group/UpdateGroupMemberName", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GroupServer).UpdateGroupMemberName(ctx, req.(*UpdateGroupMemberNameReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Group_SetAdmin_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SetAdminReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GroupServer).SetAdmin(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.group.Group/SetAdmin", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GroupServer).SetAdmin(ctx, req.(*SetAdminReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Group_UpdateGroupMemberMuteTime_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateGroupMemberMuteTimeReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GroupServer).UpdateGroupMemberMuteTime(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.group.Group/UpdateGroupMemberMuteTime", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GroupServer).UpdateGroupMemberMuteTime(ctx, req.(*UpdateGroupMemberMuteTimeReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Group_GetPriGroupInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetPriGroupInfoReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GroupServer).GetPriGroupInfo(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.group.Group/GetPriGroupInfo", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GroupServer).GetPriGroupInfo(ctx, req.(*GetPriGroupInfoReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Group_GetPubGroupInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetPubGroupInfoReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GroupServer).GetPubGroupInfo(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.group.Group/GetPubGroupInfo", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GroupServer).GetPubGroupInfo(ctx, req.(*GetPubGroupInfoReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Group_GetGroupList_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetGroupListReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GroupServer).GetGroupList(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.group.Group/GetGroupList", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GroupServer).GetGroupList(ctx, req.(*GetGroupListReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Group_GetGroupMemberList_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetGroupMemberListReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GroupServer).GetGroupMemberList(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.group.Group/GetGroupMemberList", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GroupServer).GetGroupMemberList(ctx, req.(*GetGroupMemberListReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Group_GetGroupMemberInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetGroupMemberInfoReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GroupServer).GetGroupMemberInfo(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.group.Group/GetGroupMemberInfo", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GroupServer).GetGroupMemberInfo(ctx, req.(*GetGroupMemberInfoReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Group_GetMuteList_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetMuteListReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GroupServer).GetMuteList(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.group.Group/GetMuteList", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GroupServer).GetMuteList(ctx, req.(*GetMuteListReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Group_ForceUpdateGroupType_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ForceUpdateGroupTypeReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GroupServer).ForceUpdateGroupType(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.group.Group/ForceUpdateGroupType", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GroupServer).ForceUpdateGroupType(ctx, req.(*ForceUpdateGroupTypeReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Group_ForceDisbandGroup_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ForceDisbandGroupReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GroupServer).ForceDisbandGroup(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.group.Group/ForceDisbandGroup", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GroupServer).ForceDisbandGroup(ctx, req.(*ForceDisbandGroupReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Group_ForceAddMember_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ForceAddMemberReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GroupServer).ForceAddMember(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.group.Group/ForceAddMember", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GroupServer).ForceAddMember(ctx, req.(*ForceAddMemberReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Group_ForceAddMembers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ForceAddMembersReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GroupServer).ForceAddMembers(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.group.Group/ForceAddMembers", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GroupServer).ForceAddMembers(ctx, req.(*ForceAddMembersReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Group_ForceDeleteMember_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ForceDeleteMemberReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GroupServer).ForceDeleteMember(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.group.Group/ForceDeleteMember", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GroupServer).ForceDeleteMember(ctx, req.(*ForceDeleteMemberReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Group_ForceDeleteMembers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ForceDeleteMembersReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GroupServer).ForceDeleteMembers(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.group.Group/ForceDeleteMembers", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GroupServer).ForceDeleteMembers(ctx, req.(*ForceDeleteMembersReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Group_ForceJoinGroups_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ForceJoinGroupsReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GroupServer).ForceJoinGroups(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.group.Group/ForceJoinGroups", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GroupServer).ForceJoinGroups(ctx, req.(*ForceJoinGroupsReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Group_ForceExitGroups_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ForceExitGroupsReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GroupServer).ForceExitGroups(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.group.Group/ForceExitGroups", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GroupServer).ForceExitGroups(ctx, req.(*ForceExitGroupsReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Group_ForceChangeOwner_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ForceChangeOwnerReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GroupServer).ForceChangeOwner(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.group.Group/ForceChangeOwner", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GroupServer).ForceChangeOwner(ctx, req.(*ForceChangeOwnerReq)) + } + return interceptor(ctx, in, info, handler) +} + +// Group_ServiceDesc is the grpc.ServiceDesc for Group service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Group_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "dtalk.group.Group", + HandlerType: (*GroupServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetGroupIds", + Handler: _Group_GetGroupIds_Handler, + }, + { + MethodName: "CheckInGroup", + Handler: _Group_CheckInGroup_Handler, + }, + { + MethodName: "GetMemberIds", + Handler: _Group_GetMemberIds_Handler, + }, + { + MethodName: "CheckMute", + Handler: _Group_CheckMute_Handler, + }, + { + MethodName: "GetGroups", + Handler: _Group_GetGroups_Handler, + }, + { + MethodName: "GetMember", + Handler: _Group_GetMember_Handler, + }, + { + MethodName: "GetGroupInfo", + Handler: _Group_GetGroupInfo_Handler, + }, + { + MethodName: "CreateGroup", + Handler: _Group_CreateGroup_Handler, + }, + { + MethodName: "InviteGroupMembers", + Handler: _Group_InviteGroupMembers_Handler, + }, + { + MethodName: "GroupExit", + Handler: _Group_GroupExit_Handler, + }, + { + MethodName: "GroupDisband", + Handler: _Group_GroupDisband_Handler, + }, + { + MethodName: "GroupRemove", + Handler: _Group_GroupRemove_Handler, + }, + { + MethodName: "ChangeOwner", + Handler: _Group_ChangeOwner_Handler, + }, + { + MethodName: "UpdateGroupName", + Handler: _Group_UpdateGroupName_Handler, + }, + { + MethodName: "UpdateGroupAvatar", + Handler: _Group_UpdateGroupAvatar_Handler, + }, + { + MethodName: "UpdateGroupJoinType", + Handler: _Group_UpdateGroupJoinType_Handler, + }, + { + MethodName: "UpdateGroupFriendType", + Handler: _Group_UpdateGroupFriendType_Handler, + }, + { + MethodName: "UpdateGroupMuteType", + Handler: _Group_UpdateGroupMuteType_Handler, + }, + { + MethodName: "UpdateGroupMemberName", + Handler: _Group_UpdateGroupMemberName_Handler, + }, + { + MethodName: "SetAdmin", + Handler: _Group_SetAdmin_Handler, + }, + { + MethodName: "UpdateGroupMemberMuteTime", + Handler: _Group_UpdateGroupMemberMuteTime_Handler, + }, + { + MethodName: "GetPriGroupInfo", + Handler: _Group_GetPriGroupInfo_Handler, + }, + { + MethodName: "GetPubGroupInfo", + Handler: _Group_GetPubGroupInfo_Handler, + }, + { + MethodName: "GetGroupList", + Handler: _Group_GetGroupList_Handler, + }, + { + MethodName: "GetGroupMemberList", + Handler: _Group_GetGroupMemberList_Handler, + }, + { + MethodName: "GetGroupMemberInfo", + Handler: _Group_GetGroupMemberInfo_Handler, + }, + { + MethodName: "GetMuteList", + Handler: _Group_GetMuteList_Handler, + }, + { + MethodName: "ForceUpdateGroupType", + Handler: _Group_ForceUpdateGroupType_Handler, + }, + { + MethodName: "ForceDisbandGroup", + Handler: _Group_ForceDisbandGroup_Handler, + }, + { + MethodName: "ForceAddMember", + Handler: _Group_ForceAddMember_Handler, + }, + { + MethodName: "ForceAddMembers", + Handler: _Group_ForceAddMembers_Handler, + }, + { + MethodName: "ForceDeleteMember", + Handler: _Group_ForceDeleteMember_Handler, + }, + { + MethodName: "ForceDeleteMembers", + Handler: _Group_ForceDeleteMembers_Handler, + }, + { + MethodName: "ForceJoinGroups", + Handler: _Group_ForceJoinGroups_Handler, + }, + { + MethodName: "ForceExitGroups", + Handler: _Group_ForceExitGroups_Handler, + }, + { + MethodName: "ForceChangeOwner", + Handler: _Group_ForceChangeOwner_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "group.proto", +} diff --git a/service/group/cmd/main.go b/service/group/cmd/main.go new file mode 100644 index 0000000..5818849 --- /dev/null +++ b/service/group/cmd/main.go @@ -0,0 +1,120 @@ +package main + +import ( + "context" + "flag" + "fmt" + "net" + "os" + "os/signal" + "syscall" + "time" + + "gitlab.33.cn/chat/dtalk/pkg/logger" + + "github.com/Terry-Mao/goim/pkg/ip" + "gitlab.33.cn/chat/dtalk/pkg/naming" + "gitlab.33.cn/chat/dtalk/service/group/config" + "gitlab.33.cn/chat/dtalk/service/group/server/grpc" + "gitlab.33.cn/chat/dtalk/service/group/service" +) + +const srvName = "group" + +var ( + // projectVersion 项目版本 + projectVersion = "2.3.1" + // goVersion go版本 + goVersion = "" + // gitCommit git提交commit id + gitCommit = "" + // buildTime 编译时间 + buildTime = "" + + isShowVersion = flag.Bool("v", false, "show project version") + isMaintain = flag.Bool("maintain", false, "maintain data base") +) + +// showVersion 显示项目版本信息 +func showVersion(isShow bool) { + if isShow { + fmt.Printf("Project: %s\n", srvName) + fmt.Printf(" Version: %s\n", projectVersion) + fmt.Printf(" Go Version: %s\n", goVersion) + fmt.Printf(" Git Commit: %s\n", gitCommit) + fmt.Printf(" Build Time: %s\n", buildTime) + os.Exit(0) + } +} + +func maintain(isMaintain bool, svc *service.Service) { + if isMaintain { + //err := svc.MaintainGroupAESKey() + //if err != nil { + // panic(err) + //} + os.Exit(0) + } +} + +func main() { + flag.Parse() + showVersion(*isShowVersion) + + if err := config.Init(); err != nil { + panic(err) + } + //log init + log := logger.New(config.Conf.Env, srvName) + log.Info().Interface("Config", config.Conf). + Msg("config info") + + // service init + svc := service.New(config.Conf) + maintain(*isMaintain, svc) + + // + //httpSrv := http.Init(svc) + + // rpc init + rpc := grpc.New(config.Conf.GRPCServer, svc) + + // register server + _, port, _ := net.SplitHostPort(config.Conf.GRPCServer.Addr) + addr := fmt.Sprintf("%s:%s", ip.InternalIP(), port) + if err := naming.Register(config.Conf.Reg.RegAddrs, config.Conf.Reg.SrvName, addr, config.Conf.Reg.Schema, 15); err != nil { + panic(err) + } + fmt.Println("register ok") + + // init signal + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT) + for { + s := <-c + log.Info().Str("signal", s.String()).Msg("service get a signal") + switch s { + case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT: + // 退出时从 etcd 中删除 + if err := naming.UnRegister(config.Conf.Reg.SrvName, addr, config.Conf.Reg.Schema); err != nil { + log.Error().Err(err).Msg("naming.UnRegister") + } + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + //if err := httpSrv.Shutdown(ctx); err != nil { + // log.Error().Err(err).Msg("server shutdown") + //} + //time.Sleep(time.Second * 2) + if err := rpc.Shutdown(ctx); err != nil { + log.Error().Err(err).Msg("rpc Shutdown") + } + + log.Info().Msg(srvName + " server exit") + return + case syscall.SIGHUP: + // TODO reload + default: + return + } + } +} diff --git a/service/group/config/config.go b/service/group/config/config.go new file mode 100644 index 0000000..ee35238 --- /dev/null +++ b/service/group/config/config.go @@ -0,0 +1,146 @@ +package config + +import ( + "flag" + "github.com/BurntSushi/toml" + "gitlab.33.cn/chat/dtalk/pkg/net/grpc" + xgrpc "gitlab.33.cn/chat/dtalk/pkg/net/grpc" + "gitlab.33.cn/chat/dtalk/pkg/redis" + xtime "gitlab.33.cn/chat/dtalk/pkg/time" + "time" +) + +var ( + confPath string + + Conf *Config +) + +func init() { + flag.StringVar(&confPath, "conf", "group.toml", "default config path.") +} + +// Init init config. +func Init() (err error) { + Conf = Default() + _, err = toml.DecodeFile(confPath, &Conf) + return +} + +func Default() *Config { + return &Config{ + HttpServer: &HttpServer{ + Addr: "0.0.0.0:18011", + }, + MySQL: &MySQL{ + Host: "127.0.0.1", + Port: 3306, + User: "root", + Pwd: "123456", + Db: "dtalk", + }, + Reg: &Reg{ + Schema: "dtalk", + SrvName: "group", + RegAddrs: "127.0.0.1:2379", + }, + LogicRPCClient: &RPCClient{ + RegAddrs: "127.0.0.1:2379", + Schema: "im", + SrvName: "logic", + Dial: xtime.Duration(time.Second), + Timeout: xtime.Duration(time.Second), + }, + IdGenRPCClient: &RPCClient{ + RegAddrs: "127.0.0.1:2379", + Schema: "dtalk", + SrvName: "generator", + Dial: xtime.Duration(time.Second), + Timeout: xtime.Duration(time.Second), + }, + AnswerRPCClient: &RPCClient{ + RegAddrs: "127.0.0.1:2379", + Schema: "dtalk", + SrvName: "answer", + Dial: xtime.Duration(time.Second), + Timeout: xtime.Duration(time.Second), + }, + GRPCServer: &xgrpc.ServerConfig{ + Network: "tcp", + Addr: ":18012", + Timeout: xtime.Duration(time.Second), + KeepAliveMaxConnectionIdle: xtime.Duration(time.Second * 60), + KeepAliveMaxConnectionAge: xtime.Duration(time.Hour * 2), + KeepAliveMaxMaxConnectionAgeGrace: xtime.Duration(time.Second * 20), + KeepAliveTime: xtime.Duration(time.Second * 60), + KeepAliveTimeout: xtime.Duration(time.Second * 20), + }, + GroupInfoConfig: &GroupDefault{ + GroupMaximum: 200, + AdminNum: 10, + }, + Redis: &redis.Config{ + Network: "tcp", + Addr: "127.0.0.1:6379", + Auth: "", + Active: 60000, + Idle: 1024, + DialTimeout: xtime.Duration(200 * time.Millisecond), + ReadTimeout: xtime.Duration(500 * time.Millisecond), + WriteTimeout: xtime.Duration(500 * time.Millisecond), + IdleTimeout: xtime.Duration(120 * time.Second), + Expire: xtime.Duration(30 * time.Minute), + }, + } +} + +type Config struct { + Env string + AppId string + HttpServer *HttpServer + MySQL *MySQL + Reg *Reg + LogicRPCClient *RPCClient + IdGenRPCClient *RPCClient + //PusherRPCClient *RPCClient + AnswerRPCClient *RPCClient + //gRPC server + GRPCServer *grpc.ServerConfig + GroupInfoConfig *GroupDefault + Redis *redis.Config +} + +type HttpServer struct { + Addr string +} + +type MySQL struct { + Host string + Port int32 + User string + Pwd string + Db string +} + +// Reg is service register/discovery config +type Reg struct { + Schema string + SrvName string // call + RegAddrs string // etcd addrs, seperate by ',' +} + +// RPCClient is RPC client config. +type RPCClient struct { + RegAddrs string + Schema string + SrvName string // call + Dial xtime.Duration + Timeout xtime.Duration +} + +type GroupDefault struct { + // 群人数上限 [200, 2000] + GroupMaximum int32 + // 群管理员人数上限 [10, 10] + AdminNum int32 +} diff --git a/service/group/config/group.toml b/service/group/config/group.toml new file mode 100644 index 0000000..3616123 --- /dev/null +++ b/service/group/config/group.toml @@ -0,0 +1,64 @@ +Env="debug" +AppId= "dtalk" + +[HttpServer] +Addr="0.0.0.0:18011" + +[MySQL] +Host = "172.16.101.107" +Port = 3306 +User= "root" +Pwd= "123456" +Db= "dtalk" + +[Reg] +Schema = "dtalk" +SrvName = "group" +RegAddrs = "127.0.0.1:2379" + +[LogicRPCClient] +RegAddrs = "172.16.101.107:2379" +Schema = "im" +SrvName = "logic" +Dial = "1s" +Timeout = "1s" + +[IdGenRPCClient] +RegAddrs = "172.16.101.107:2379" +Schema = "dtalk" +SrvName = "generator" +Dial = "1s" +Timeout = "1s" + +[AnswerRPCClient] +RegAddrs = "172.16.101.107:2379" +Schema = "dtalk" +SrvName = "answer" +Dial = "1s" +Timeout = "1s" + +[GRPCServer] +Network= "tcp" +Addr= ":18012" +Timeout= "1s" +KeepAliveMaxConnectionIdle= "60s" +KeepAliveMaxConnectionAge= "2h" +KeepAliveMaxMaxConnectionAgeGrace= "20s" +KeepAliveTime= "60s" +KeepAliveTimeout= "20s" + +[Group] +GroupMaximum = 2000 +AdminNum = 10 + +[Redis] +network = "tcp" +addr = "127.0.0.1:6379" +auth = "" +active = 60000 +idle = 1024 +dialTimeout = "200ms" +readTimeout = "500ms" +writeTimeout = "500ms" +idleTimeout = "120s" +expire = "30m" diff --git a/service/group/dao/dao.go b/service/group/dao/dao.go new file mode 100644 index 0000000..cd38b80 --- /dev/null +++ b/service/group/dao/dao.go @@ -0,0 +1,76 @@ +package dao + +import ( + "context" + "fmt" + "os" + "time" + + "github.com/rs/zerolog" + zlog "github.com/rs/zerolog/log" + "gitlab.33.cn/chat/dtalk/pkg/api/trace" + "gitlab.33.cn/chat/dtalk/pkg/mysql" + "gitlab.33.cn/chat/dtalk/pkg/redis" + "gitlab.33.cn/chat/dtalk/service/group/config" + "gitlab.33.cn/utils/go-kit/math" +) + +var srvName = "group/dao" + +type Dao struct { + log zerolog.Logger + conn *mysql.MysqlConn + // redis 连接 + redis *redis.Pool + // redis 过期时间 + redisExpire time.Duration +} + +func New(c *config.Config) *Dao { + d := &Dao{ + log: zlog.Logger.With().Str("service", srvName).Logger(), + conn: newDB(c.MySQL), + redis: redis.New(c.Redis), + redisExpire: time.Duration(c.Redis.Expire), + } + //log init + zerolog.SetGlobalLevel(zerolog.InfoLevel) + if config.Conf.Env == "debug" { + d.log = zlog.Output(zerolog.ConsoleWriter{Out: os.Stdout}).With().Str("service", srvName).Logger() + zerolog.SetGlobalLevel(zerolog.DebugLevel) + } else if config.Conf.Env == "benchmark" { + zerolog.SetGlobalLevel(zerolog.Disabled) + } + + return d +} + +func newDB(cfg *config.MySQL) *mysql.MysqlConn { + c, err := mysql.NewMysqlConn(cfg.Host, fmt.Sprintf("%v", cfg.Port), + cfg.User, cfg.Pwd, cfg.Db, "UTF8MB4") + if err != nil { + panic(err) + } + return c +} + +func (d *Dao) NewTx() (*mysql.MysqlTx, error) { + return d.conn.NewTx() +} + +func (d *Dao) GetLogWithTrace(ctx context.Context) zerolog.Logger { + logId := d.GetTrace(ctx) + return d.log.With().Str("trace", logId).Logger() +} + +func (d *Dao) GetTrace(ctx context.Context) string { + return trace.NewTraceIdWithContext(ctx) +} + +func (d *Dao) GetRandRedisExpire() time.Duration { + return (time.Duration(math.RandInt(0, 100))*time.Second + time.Duration(d.redisExpire)) / time.Second +} + +func (d *Dao) getNowTime() int64 { + return time.Now().UnixNano() / 1e6 +} diff --git a/service/group/dao/dao_test.go b/service/group/dao/dao_test.go new file mode 100644 index 0000000..5bf29d8 --- /dev/null +++ b/service/group/dao/dao_test.go @@ -0,0 +1,27 @@ +package dao + +import ( + "os" + "testing" + + "github.com/inconshreveable/log15" + "gitlab.33.cn/chat/dtalk/pkg/mysql" + "gitlab.33.cn/chat/dtalk/service/group/config" +) + +var ( + testLog log15.Logger + testConn *mysql.MysqlConn +) + +func TestMain(m *testing.M) { + testConn = newDB(&config.MySQL{ + Host: "127.0.0.1", + Port: 3306, + User: "root", + Pwd: "123456", + Db: "dtalk", + }) + testLog = log15.New("model", "group/test") + os.Exit(m.Run()) +} diff --git a/service/group/dao/data.go b/service/group/dao/data.go new file mode 100644 index 0000000..415cb7f --- /dev/null +++ b/service/group/dao/data.go @@ -0,0 +1,417 @@ +package dao + +import ( + "context" + + "github.com/pkg/errors" + "gitlab.33.cn/chat/dtalk/pkg/mysql" + "gitlab.33.cn/chat/dtalk/service/group/model/biz" + "gitlab.33.cn/chat/dtalk/service/group/model/db" + "gitlab.33.cn/utils/go-kit/convert" +) + +// 数据读写层,数据库和缓存全部在这层统一处理,包括 cache miss 处理。 + +// GetAllGroupInfo . +func (d *Dao) GetAllGroupInfo() ([]*biz.GroupInfo, error) { + groupPos, err := d.getAllGroupInfo() + if err != nil { + return nil, err + } + groups := make([]*biz.GroupInfo, 0, len(groupPos)) + for i := range groupPos { + groups = append(groups, groupPos[i].ToBiz()) + } + return groups, nil +} + +// GetGroupInfoByGroupMarkId 查询群信息 +func (d *Dao) GetGroupInfoByGroupMarkId(groupMarkId string) (*biz.GroupInfo, error) { + // redis get + groupInfo, err := d.getGroupInfoByGroupMarkId(groupMarkId) + if err != nil { + return nil, err + } + // redis put + return groupInfo.ToBiz(), nil +} + +// GetGroupInfoByGroupId 查询群信息 +func (d *Dao) GetGroupInfoByGroupId(ctx context.Context, groupId int64) (*biz.GroupInfo, error) { + log := d.GetLogWithTrace(ctx) + // 加缓存 get + group, err := d.GetGroupCache(groupId) + if err != nil { + //log.Warn().Err(err).Int64("groupId", groupId).Msg("GetGroupCache err") + } else { + return group, nil + } + + groupInfo, err := d.getGroupInfoByGroupId(groupId) + if err != nil { + return nil, err + } + res := groupInfo.ToBiz() + + muteNum, err := d.GetGroupMuteNum(groupId) + if err != nil { + return nil, err + } + res.MuteNum = muteNum + + adminNum, err := d.getAdminNumByGroupId(groupId) + if err != nil { + return nil, err + } + res.AdminNum = adminNum + + // 加缓存 set 设置固定值+随机的过期时间 + expire := d.GetRandRedisExpire() + err = d.SaveGroup(res, expire) + if err != nil { + log.Warn().Err(err).Int64("groupId", groupId).Msg("SaveGroup err") + } + return res, nil +} + +// GetGroupInfosByGroupIds 查询群信息s +func (d *Dao) GetGroupInfosByGroupIds(ctx context.Context, groupIds []int64) ([]*biz.GroupInfo, error) { + + groupInfos, err := d.getGroupInfosByGroupIds(groupIds) + if err != nil { + return nil, err + } + + ress := make([]*biz.GroupInfo, 0, len(groupInfos)) + for _, groupInfo := range groupInfos { + ress = append(ress, groupInfo.ToBiz()) + } + + return ress, nil +} + +// GetGroupIdsByMemberId 查询用户所有加入的群 ID +func (d *Dao) GetGroupIdsByMemberId(memberId string) ([]int64, error) { + groupIds, err := d.getGroupIdsByMemberId(memberId) + if err != nil { + return nil, errors.WithMessagef(err, "getGroupIdsByMemberId memberId=%s", memberId) + } + return groupIds, nil +} + +// GetMembersByGroupId 查询群里的所有成员信息 +func (d *Dao) GetMembersByGroupId(groupId int64) ([]*biz.GroupMember, error) { + res, err := d.getMembersByGroupId(groupId) + if err != nil { + return nil, err + } + + groupMembers := make([]*biz.GroupMember, 0) + for _, groupMember := range res { + groupMembers = append(groupMembers, groupMember.ToBiz()) + } + return groupMembers, nil +} + +// GetGroupMembersByGroupIdWithLimit 查询群内前 n 个群成员信息 +func (d *Dao) GetGroupMembersByGroupIdWithLimit(groupId, n, m int64) ([]*biz.GroupMember, error) { + members, err := d.getMembersByGroupIdWithLimit(groupId, n, m) + if err != nil { + return nil, err + } + + groupMembers := make([]*biz.GroupMember, 0) + for _, groupMember := range members { + groupMembers = append(groupMembers, groupMember.ToBiz()) + } + return groupMembers, nil +} + +// UpdateGroupInfoMemberNum 更新群成员数量 +func (d *Dao) UpdateGroupInfoMemberNum(ctx context.Context, groupId int64) (int32, error) { + log := d.GetLogWithTrace(ctx) + members, err := d.GetMembersByGroupId(groupId) + if err != nil { + return 0, errors.WithMessagef(err, "GetMembersByGroupId, groupId=%d", groupId) + } + newNum := convert.ToInt32(len(members)) + nowTime := d.getNowTime() + + groupInfo := &db.GroupInfo{ + GroupId: groupId, + GroupMemberNum: newNum, + GroupUpdateTime: nowTime, + } + _, _, err = d.updateGroupInfoMemberNum(groupInfo) + if err != nil { + return 0, errors.WithMessagef(err, "UpdateGroupInfoMemberNum, groupId=%d", groupId) + } + + // 删 group 缓存 + err = d.DeleteGroupCache(groupId) + if err != nil { + log.Err(err).Int64("groupId", groupId).Msg("DeleteGroupCache err") + } + + return newNum, nil +} + +// UpdateGroupInfoName 更新群名称 +func (d *Dao) UpdateGroupInfoName(ctx context.Context, groupId int64, name, publicName string) error { + log := d.GetLogWithTrace(ctx) + nowTime := d.getNowTime() + groupInfo := &db.GroupInfo{ + GroupId: groupId, + GroupName: name, + GroupUpdateTime: nowTime, + GroupPubName: publicName, + } + if _, _, err := d.updateGroupInfoName(groupInfo); err != nil { + return errors.WithMessagef(err, "UpdateGroupInfoName, groupInfo=%+v", groupInfo) + } + + // 删 group 缓存 + err := d.DeleteGroupCache(groupId) + if err != nil { + log.Warn().Err(err).Int64("groupId", groupId).Msg("DeleteGroupCache err") + } + return nil +} + +// UpdateGroupInfoAvatar 更新群头像 +func (d *Dao) UpdateGroupInfoAvatar(ctx context.Context, groupId int64, avatar string) error { + log := d.GetLogWithTrace(ctx) + nowTime := d.getNowTime() + groupInfo := &db.GroupInfo{ + GroupId: groupId, + GroupAvatar: avatar, + GroupUpdateTime: nowTime, + } + if _, _, err := d.updateGroupInfoAvatar(groupInfo); err != nil { + return errors.WithMessagef(err, "updateGroupInfoAvatar, groupInfo=%+v", groupInfo) + } + + // 删 group 缓存 + err := d.DeleteGroupCache(groupId) + if err != nil { + log.Warn().Err(err).Int64("groupId", groupId).Msg("DeleteGroupCache err") + } + return nil +} + +// UpdateGroupInfoJoinType 更新加群设置 +func (d *Dao) UpdateGroupInfoJoinType(ctx context.Context, groupId int64, joinType int32) error { + log := d.GetLogWithTrace(ctx) + nowTime := d.getNowTime() + groupInfo := &db.GroupInfo{ + GroupId: groupId, + GroupJoinType: joinType, + GroupUpdateTime: nowTime, + } + if _, _, err := d.updateGroupInfoJoinType(groupInfo); err != nil { + return errors.WithMessagef(err, "updateGroupInfoJoinType, groupInfo=%+v", groupInfo) + } + + // 删 group 缓存 + err := d.DeleteGroupCache(groupId) + if err != nil { + log.Warn().Err(err).Int64("groupId", groupId).Msg("DeleteGroupCache err") + } + + return nil +} + +// UpdateGroupInfoFriendType 更新群内加好友设置 +func (d *Dao) UpdateGroupInfoFriendType(ctx context.Context, groupId int64, friendType int32) error { + log := d.GetLogWithTrace(ctx) + nowTime := d.getNowTime() + groupInfo := &db.GroupInfo{ + GroupId: groupId, + GroupFriendType: friendType, + GroupUpdateTime: nowTime, + } + if _, _, err := d.updateGroupInfoFriendType(groupInfo); err != nil { + return errors.WithMessagef(err, "UpdateGroupInfoFriendType, groupInfo=%+v", groupInfo) + } + + // 删 group 缓存 + err := d.DeleteGroupCache(groupId) + if err != nil { + log.Warn().Err(err).Int64("groupId", groupId).Msg("DeleteGroupCache err") + } + + return nil +} + +// UpdateGroupInfoMuteType 更新群禁言设置 +func (d *Dao) UpdateGroupInfoMuteType(ctx context.Context, groupId int64, muteType int32) error { + log := d.GetLogWithTrace(ctx) + nowTime := d.getNowTime() + groupInfo := &db.GroupInfo{ + GroupId: groupId, + GroupMuteType: muteType, + GroupUpdateTime: nowTime, + } + if _, _, err := d.updateGroupInfoMuteType(groupInfo); err != nil { + return errors.WithMessagef(err, "UpdateGroupInfoMuteType, groupInfo=%+v", groupInfo) + } + + // 删 group 缓存 + err := d.DeleteGroupCache(groupId) + if err != nil { + log.Warn().Err(err).Int64("groupId", groupId).Msg("DeleteGroupCache err") + } + + return nil +} + +//UpdateGroupMemberName 更新群成员昵称 +func (d *Dao) UpdateGroupMemberName(groupId int64, memberId, memberName string) error { + nowTime := d.getNowTime() + groupMember := &db.GroupMember{ + GroupId: groupId, + GroupMemberId: memberId, + GroupMemberName: memberName, + GroupMemberUpdateTime: nowTime, + } + if _, _, err := d.updateGroupMemberName(groupMember); err != nil { + return errors.WithMessagef(err, "UpdateGroupMemberName, groupMember=%+v", groupMember) + } + return nil +} + +// UpdateGroupMemberType 更新群成员类型 +func (d *Dao) UpdateGroupMemberType(ctx context.Context, groupId int64, mnemberId string, memberType int32) error { + log := d.GetLogWithTrace(ctx) + + nowTime := d.getNowTime() + groupMember := &db.GroupMember{ + GroupId: groupId, + GroupMemberId: mnemberId, + GroupMemberType: memberType, + GroupMemberUpdateTime: nowTime, + } + if _, _, err := d.updateGroupMemberType(groupMember); err != nil { + return errors.WithMessagef(err, "UpdateGroupMemberType, groupMember=%+v", groupMember) + } + + err := d.DeleteGroupCache(groupId) + if err != nil { + log.Warn().Err(err).Int64("groupId", groupId).Msg("DeleteGroupCache err") + } + return nil +} + +// GetAdminNumByGroupId 查询群内管理员数量 +func (d *Dao) GetAdminNumByGroupId(groupId int64) (int32, error) { + num, err := d.getAdminNumByGroupId(groupId) + if err != nil { + return 0, errors.WithMessagef(err, "GetAdminNumByGroupId, groupId=%d", groupId) + } + return num, nil +} + +// InsertGroupMembers 批量插入群成员 +func (d *Dao) InsertGroupMembers(tx *mysql.MysqlTx, groupMembers []*db.GroupMember) error { + if len(groupMembers) == 0 { + return nil + } + + _, _, err := d.insertGroupMembers(tx, groupMembers) + if err != nil { + return errors.WithMessagef(err, "InsertGroupMembers, groupMembers=%v", groupMembers) + } + + _ = d.DeleteGroupCache(groupMembers[0].GroupId) + return nil +} + +// GetGroupMemberMuteTime 查询群成员禁言时间 +func (d *Dao) GetGroupMemberMuteTime(groupId int64, memberId string) (int64, error) { + muteTime, err := d.getGroupMemberMuteTime(groupId, memberId) + if err != nil { + return 0, errors.WithMessagef(err, "GetGroupMemberMuteTime groupId=%d, memberId=%s", groupId, memberId) + } + return muteTime, nil +} + +// GetGroupMuteNum 查询群内禁言人数 +func (d *Dao) GetGroupMuteNum(groupId int64) (int32, error) { + nowTime := d.getNowTime() + muteNum, err := d.getGroupMuteNum(groupId, nowTime) + if err != nil { + return 0, errors.WithMessagef(err, "GetGroupMuteNum groupId=%d, nowTime=%d", groupId, nowTime) + } + return muteNum, nil +} + +// GetGroupMemberByGroupIdAndMemberId 查询一个群成员信息 +func (d *Dao) GetGroupMemberByGroupIdAndMemberId(ctx context.Context, groupId int64, memberId string) (*biz.GroupMember, error) { + //log := d.GetLogWithTrace(ctx) + //加缓存 get + //groupMember, err := d.GetGroupMemberWithMuteTime(groupId, memberId) + //if err != nil { + // log.Warn().Err(err).Int64("groupId", groupId).Str("memberId", memberId).Msg("GetGroupMemberWithMuteTime") + //} else { + // return groupMember.ToBiz(), nil + //} + + groupMember, err := d.getGroupMemberWithMuteTime(groupId, memberId) + if err != nil { + return nil, err + } + + // 加缓存 set 设置固定值+随机的过期时间 + //expire := d.GetRandRedisExpire() + //err = d.SaveGroupMemberWithMuteTime(groupMember, expire) + //if err != nil { + // log.Warn().Err(err).Int64("groupId", groupId).Str("memberId", memberId).Msg("SaveGroupMemberWithMuteTime") + //} + return groupMember.ToBiz(), nil +} + +// GetGroupMembersMutedByGroupId 查询群内被禁言的群成员信息 +func (d *Dao) GetGroupMembersMutedByGroupId(groupId int64) ([]*biz.GroupMember, error) { + nowTime := d.getNowTime() + muteList, err := d.getGroupMuteList(groupId, nowTime) + if err != nil { + return nil, err + } + + groupMembers := make([]*biz.GroupMember, 0) + for _, groupMember := range muteList { + groupMembers = append(groupMembers, groupMember.ToBiz()) + } + return groupMembers, nil +} + +// GetGroupMemberWithMuteTimeByGroupIdAndMemberId 查询一个带禁言时间的群成员信息 +// 没用 删掉 +func (d *Dao) GetGroupMemberWithMuteTimeByGroupIdAndMemberId(groupId int64, memberId string) (*biz.GroupMember, error) { + member, err := d.getGroupMemberWithMuteTime(groupId, memberId) + if err != nil { + err = errors.WithMessagef(err, "GetGroupMemberWithMuteTimeByGroupIdAndMemberId GetGroupMemberWithMuteTime groupId=%d, memberId=%s", groupId, memberId) + return nil, err + } + return member.ToBiz(), nil +} + +func (d *Dao) UpdateGroupMemberMuteTimes(ctx context.Context, tx *mysql.MysqlTx, groupMemberMutes []*db.GroupMemberMute) error { + if len(groupMemberMutes) == 0 { + return nil + } + + log := d.GetLogWithTrace(ctx) + + if _, _, err := d.updateGroupMemberMuteTimes(tx, groupMemberMutes); err != nil { + return errors.WithMessagef(err, "UpdateGroupMemberMuteTimes, groupMemberMutes=%+v", groupMemberMutes) + } + + // 删 group 缓存 + groupId := groupMemberMutes[0].GroupId + err := d.DeleteGroupCache(groupId) + if err != nil { + log.Warn().Err(err).Int64("groupId", groupId).Msg("DeleteGroupCache err") + } + return nil +} diff --git a/service/group/dao/db.go b/service/group/dao/db.go new file mode 100644 index 0000000..415f268 --- /dev/null +++ b/service/group/dao/db.go @@ -0,0 +1,470 @@ +package dao + +import ( + "gitlab.33.cn/chat/dtalk/pkg/mysql" + "gitlab.33.cn/chat/dtalk/service/group/model" + "gitlab.33.cn/chat/dtalk/service/group/model/biz" + "gitlab.33.cn/chat/dtalk/service/group/model/db" + "gitlab.33.cn/utils/go-kit/convert" + "strings" +) + +const ( + // dtalk_group_info + _InsertGroupInfo = `INSERT INTO dtalk_group_info ( group_id, group_mark_id, group_name, group_avatar, group_member_num, group_maximum, + group_introduce, group_status, group_owner_id, group_create_time, group_update_time, group_join_type, + group_mute_type, group_friend_type, group_aes_key, group_pub_name, group_type ) + VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )` + _UpdateGroupInfoName = `UPDATE dtalk_group_info SET group_name=?, group_pub_name=?, group_update_time=? + WHERE group_id=?` + _UpdateGroupInfoAvatar = `UPDATE dtalk_group_info SET group_avatar=?, group_update_time=? + WHERE group_id=?` + _UpdateGroupInfoGroupNum = `UPDATE dtalk_group_info SET group_member_num=?, group_update_time=? + WHERE group_id=?` + _UpdateGroupInfoMaximum = `UPDATE dtalk_group_info SET group_maximum=?, group_update_time=? + WHERE group_id=?` + _UpdateGroupInfoIntroduce = `UPDATE dtalk_group_info SET group_introduce=?, group_update_time=? + WHERE group_id=?` + _UpdateGroupInfoStatus = `UPDATE dtalk_group_info SET group_status=?, group_update_time=? + WHERE group_id=?` + _UpdateGroupInfoOwnerId = `UPDATE dtalk_group_info SET group_owner_id=?, group_update_time=? + WHERE group_id=?` + _UpdateGroupInfoJoinType = `UPDATE dtalk_group_info SET group_join_type=?, group_update_time=? + WHERE group_id=?` + _UpdateGroupInfoMuteType = `UPDATE dtalk_group_info SET group_mute_type=?, group_update_time=? + WHERE group_id=?` + _UpdateGroupInfoFriendType = `UPDATE dtalk_group_info SET group_friend_type=?, group_update_time=? + WHERE group_id=?` + _GetGroupInfoByGroupId = `SELECT * FROM dtalk_group_info WHERE group_id=?` + _GetGroupInfosByGroupIds = `SELECT * FROM dtalk_group_info WHERE group_id IN (?)` + _GetGroupInfoByGroupMarkId = `SELECT * FROM dtalk_group_info WHERE group_mark_id=?` + _GetAllGroupInfo = `SELECT * FROM dtalk_group_info` + _MaintainAESKeyAndPubName = `UPDATE dtalk_group_info SET group_aes_key=?, group_pub_name=? WHERE group_id=?` + _MaintainGroupType = `UPDATE dtalk_group_info SET group_type=?, group_join_type=? WHERE group_id=?` + + // dtalk_group_member + _InsertGroupMember = `INSERT INTO dtalk_group_member ( group_id, group_member_id, group_member_name, group_member_type, + group_member_join_time, group_member_update_time) VALUES ( ?, ?, ?, ?, ?, ? ) + ON DUPLICATE KEY UPDATE group_member_type=?, group_member_join_time=?, + group_member_update_time=?` + _InsertGroupMembersPrefix = `INSERT INTO dtalk_group_member ( group_id, group_member_id, group_member_name, group_member_type, + group_member_join_time, group_member_update_time) VALUES ` + _InsertGroupMembersSuffix = `ON DUPLICATE KEY UPDATE group_member_name='', group_member_type=?, group_member_join_time=?, + group_member_update_time=?` + _UpdateGroupMemberName = `UPDATE dtalk_group_member SET group_member_name=?, group_member_update_time=? + WHERE group_id=? AND group_member_id=?` + _UpdateGroupMemberType = `UPDATE dtalk_group_member SET group_member_type=?, group_member_update_time=? + WHERE group_id=? AND group_member_id=?` + _GetGroupIdsByMemberId = `SELECT group_id FROM dtalk_group_member WHERE group_member_id=? AND group_member_type?` + _GetGroupMembersMuted = `SELECT +mem.group_id as group_id, +mem.group_member_id as group_member_id, +mem.group_member_type as group_member_type, +mem.group_member_name as group_member_name, +mute.group_member_mute_time as group_member_mute_time +From dtalk_group_member AS mem LEFT JOIN dtalk_group_member_mute AS mute +ON mem.group_id=mute.group_id AND mem.group_member_id=mute.group_member_id +WHERE mem.group_id=? AND mem.group_member_type?` + _GetGroupMemberWithMuteTime = `SELECT +mem.group_id as group_id, +mem.group_member_id as group_member_id, +mem.group_member_type as group_member_type, +mem.group_member_name as group_member_name, +mem.group_member_join_time as group_member_join_time, +mute.group_member_mute_time as group_member_mute_time +From dtalk_group_member AS mem LEFT JOIN dtalk_group_member_mute AS mute +ON mem.group_id=mute.group_id AND mem.group_member_id=mute.group_member_id +WHERE mem.group_id=? AND mem.group_member_id=? AND mem.group_member_type 1 { + // 转让群主, 再到下面一起退群 + // todo + err = l.svc.ChangeOwner(l.ctx, group, deleteMember, members[1]) + if err != nil { + return nil, err + } + } else { + // 解散群 + err = l.svc.GroupDisband(l.ctx, groupId, memberId) + if err != nil { + return nil, err + } + + return &pb.ForceDeleteMemberResp{}, nil + } + } + + // 退群 + err = l.svc.RemoveGroupMembers(l.ctx, group, []*biz.GroupMember{deleteMember}) + if err != nil { + return nil, err + } + + return &pb.ForceDeleteMemberResp{}, nil +} diff --git a/service/group/logic/force_delete_members.go b/service/group/logic/force_delete_members.go new file mode 100644 index 0000000..7e85fbb --- /dev/null +++ b/service/group/logic/force_delete_members.go @@ -0,0 +1,101 @@ +package logic + +import ( + "context" + "github.com/rs/zerolog" + "gitlab.33.cn/chat/dtalk/service/group/model/biz" + + pb "gitlab.33.cn/chat/dtalk/service/group/api" + + "gitlab.33.cn/chat/dtalk/service/group/service" +) + +type ForceDeleteMembersLogic struct { + ctx context.Context + svc *service.Service + log zerolog.Logger +} + +func NewForceDeleteMembersLogic(ctx context.Context, svc *service.Service) *ForceDeleteMembersLogic { + return &ForceDeleteMembersLogic{ + ctx: ctx, + svc: svc, + log: svc.GetLog(), + } +} + +// ForceDeleteMembers 多个人退出同一个群 +func (l *ForceDeleteMembersLogic) ForceDeleteMembers(req *pb.ForceDeleteMembersReq) (*pb.ForceDeleteMembersResp, error) { + groupId := req.GroupId + + // 判断群是否存在 + group, err := l.svc.GetGroupInfoByGroupId(l.ctx, groupId) + if err != nil { + return nil, err + } + + needDeleteMembers := l.svc.GetFilteredGroupMembers(l.ctx, group, FilteredMemberIds(req.MemberIds)) + if len(needDeleteMembers) == 0 { + return &pb.ForceDeleteMembersResp{}, nil + } + + members, err := l.svc.GetGroupMembersByGroupIdWithLimit(group.GroupId, 0, int64(len(needDeleteMembers))+1) + if err != nil { + return nil, err + } + + if len(needDeleteMembers) == len(members) { + // 解散群 + err = l.svc.GroupDisband(l.ctx, groupId, l.svc.GetOpe(l.ctx)) + if err != nil { + return nil, err + } + + return &pb.ForceDeleteMembersResp{}, nil + } + + owner, ok := hasOwner(needDeleteMembers) + if ok { + CandidateOwner := getCandidateOwner(members, needDeleteMembers) + err = l.svc.ChangeOwner(l.ctx, group, owner, CandidateOwner) + if err != nil { + return nil, err + } + } + + // 退群 + err = l.svc.RemoveGroupMembers(l.ctx, group, needDeleteMembers) + if err != nil { + return nil, err + } + + return &pb.ForceDeleteMembersResp{}, nil +} + +func hasOwner(members []*biz.GroupMember) (*biz.GroupMember, bool) { + for _, member := range members { + if err := member.IsOwner(); err == nil { + return member, true + } + } + + return nil, false +} + +func getCandidateOwner(members []*biz.GroupMember, deleteMembers []*biz.GroupMember) *biz.GroupMember { + for i := 0; i < len(members); i++ { + isDelete := false + for j := 0; j < len(deleteMembers); j++ { + if members[i].GroupMemberId == deleteMembers[j].GroupMemberId { + isDelete = true + break + } + } + + if !isDelete { + return members[i] + } + } + + return &biz.GroupMember{} +} diff --git a/service/group/logic/force_exit_groups.go b/service/group/logic/force_exit_groups.go new file mode 100644 index 0000000..db3683c --- /dev/null +++ b/service/group/logic/force_exit_groups.go @@ -0,0 +1,45 @@ +package logic + +import ( + "context" + "github.com/rs/zerolog" + pb "gitlab.33.cn/chat/dtalk/service/group/api" + "gitlab.33.cn/chat/dtalk/service/group/service" +) + +type ForceExitGroupsLogic struct { + ctx context.Context + svc *service.Service + log zerolog.Logger +} + +func NewForceExitGroupsLogic(ctx context.Context, svc *service.Service) *ForceExitGroupsLogic { + return &ForceExitGroupsLogic{ + ctx: ctx, + svc: svc, + log: svc.GetLog(), + } +} + +// ForceExitGroups . +// todo: 没有好的想法 +// 相当于多次 DeleteMember +func (l *ForceExitGroupsLogic) ForceExitGroups(req *pb.ForceExitGroupsReq) (*pb.ForceExitGroupsResp, error) { + _, err := FilteredMemberId(req.Member.Id) + if err != nil { + return nil, err + } + + dml := NewForceDeleteMemberLogic(l.ctx, l.svc) + for _, groupId := range req.GroupIds { + _, err := dml.ForceDeleteMember(&pb.ForceDeleteMemberReq{ + MemberId: req.Member.Id, + GroupId: groupId, + }) + if err != nil { + l.log.Error().Err(err).Msg("ForceExitGroups") + } + } + + return &pb.ForceExitGroupsResp{}, nil +} diff --git a/service/group/logic/force_join_groups.go b/service/group/logic/force_join_groups.go new file mode 100644 index 0000000..77d47da --- /dev/null +++ b/service/group/logic/force_join_groups.go @@ -0,0 +1,53 @@ +package logic + +import ( + "context" + pb "gitlab.33.cn/chat/dtalk/service/group/api" + "gitlab.33.cn/chat/dtalk/service/group/model/biz" + "gitlab.33.cn/chat/dtalk/service/group/service" +) + +type ForceJoinGroupsLogic struct { + ctx context.Context + svc *service.Service +} + +func NewForceJoinGroupsLogic(ctx context.Context, svc *service.Service) *ForceJoinGroupsLogic { + return &ForceJoinGroupsLogic{ + ctx: ctx, + svc: svc, + } +} + +// ForceJoinGroups 一个人加入多个群 +// 相当于多次 AddMember +func (l *ForceJoinGroupsLogic) ForceJoinGroups(req *pb.ForceJoinGroupsReq) (*pb.ForceJoinGroupsResp, error) { + _, err := FilteredMemberId(req.Member.Id) + if err != nil { + return nil, err + } + + groups := make([]*biz.GroupInfo, 0, len(req.GroupIds)) + for _, groupId := range req.GroupIds { + group, err := l.svc.GetGroupInfoByGroupId(l.ctx, groupId) + if err != nil { + continue + } + + groups = append(groups, group) + } + + members := make([]*biz.GroupMember, 0, len(groups)) + for _, group := range groups { + members = append(members, &biz.GroupMember{ + GroupId: group.GroupId, + GroupMemberId: req.Member.Id, + GroupMemberName: req.Member.Name, + GroupMemberType: biz.GroupMemberTypeNormal, + }) + } + + l.svc.JoinGroups(l.ctx, members) + + return &pb.ForceJoinGroupsResp{}, nil +} diff --git a/service/group/logic/get_group_list.go b/service/group/logic/get_group_list.go new file mode 100644 index 0000000..8c8cf7a --- /dev/null +++ b/service/group/logic/get_group_list.go @@ -0,0 +1,52 @@ +package logic + +import ( + "context" + pb "gitlab.33.cn/chat/dtalk/service/group/api" + "gitlab.33.cn/chat/dtalk/service/group/service" +) + +type GetGroupListLogic struct { + ctx context.Context + svc *service.Service +} + +func NewGetGroupListLogic(ctx context.Context, svc *service.Service) *GetGroupListLogic { + return &GetGroupListLogic{ + ctx: ctx, + svc: svc, + } +} + +// GetGroupList 查询加入的群列表 +func (l *GetGroupListLogic) GetGroupList(req *pb.GetGroupListReq) (*pb.GetGroupListResp, error) { + groupIds, err := l.svc.GetGroupIdsByMemberId(req.PersonId) + if err != nil { + return nil, err + } + + groupInfos := make([]*pb.GroupBizInfo, 0, len(groupIds)) + for _, groupId := range groupIds { + group, err := l.svc.GetGroupInfoByGroupId(l.ctx, groupId) + if err != nil { + return nil, err + } + + owner, err := l.svc.GetMemberByMemberIdAndGroupId(l.ctx, group.GroupOwnerId, group.GroupId) + if err != nil { + return nil, err + } + person, err := l.svc.GetPersonByMemberIdAndGroupId(l.ctx, req.PersonId, group.GroupId) + if err != nil { + return nil, err + } + groupInfo := NewRPCGroupInfo(group) + groupInfo.Owner = NewRPCGroupMemberInfo(owner) + groupInfo.Person = NewRPCGroupMemberInfo(person) + groupInfos = append(groupInfos, groupInfo) + } + + return &pb.GetGroupListResp{ + Groups: groupInfos, + }, nil +} diff --git a/service/group/logic/get_group_member_info.go b/service/group/logic/get_group_member_info.go new file mode 100644 index 0000000..e790096 --- /dev/null +++ b/service/group/logic/get_group_member_info.go @@ -0,0 +1,45 @@ +package logic + +import ( + "context" + pb "gitlab.33.cn/chat/dtalk/service/group/api" + "gitlab.33.cn/chat/dtalk/service/group/service" +) + +type GetGroupMemberInfoLogic struct { + ctx context.Context + svc *service.Service +} + +func NewGetGroupMemberInfoLogic(ctx context.Context, svc *service.Service) *GetGroupMemberInfoLogic { + return &GetGroupMemberInfoLogic{ + ctx: ctx, + svc: svc, + } +} + +// GetGroupMemberInfo 查询一个人的信息 +func (l *GetGroupMemberInfoLogic) GetGroupMemberInfo(req *pb.GetGroupMemberInfoReq) (*pb.GetGroupMemberInfoResp, error) { + groupId := req.GroupId + memberId := req.MemberId + personId := req.PersonId + + _, err := l.svc.GetGroupInfoByGroupId(l.ctx, groupId) + if err != nil { + return nil, err + } + + _, err = l.svc.GetPersonByMemberIdAndGroupId(l.ctx, personId, groupId) + if err != nil { + return nil, err + } + + member, err := l.svc.GetMemberByMemberIdAndGroupId(l.ctx, memberId, groupId) + if err != nil { + return nil, err + } + + return &pb.GetGroupMemberInfoResp{ + Member: NewRPCGroupMemberInfo(member), + }, nil +} diff --git a/service/group/logic/get_group_member_list.go b/service/group/logic/get_group_member_list.go new file mode 100644 index 0000000..54ea660 --- /dev/null +++ b/service/group/logic/get_group_member_list.go @@ -0,0 +1,44 @@ +package logic + +import ( + "context" + pb "gitlab.33.cn/chat/dtalk/service/group/api" + "gitlab.33.cn/chat/dtalk/service/group/service" +) + +type GetGroupMemberListLogic struct { + ctx context.Context + svc *service.Service +} + +func NewGetGroupMemberListLogic(ctx context.Context, svc *service.Service) *GetGroupMemberListLogic { + return &GetGroupMemberListLogic{ + ctx: ctx, + svc: svc, + } +} + +// GetGroupMemberList 查询群成员列表 +func (l *GetGroupMemberListLogic) GetGroupMemberList(req *pb.GetGroupMemberListReq) (*pb.GetGroupMemberListResp, error) { + groupId := req.GroupId + personId := req.PersonId + + _, err := l.svc.GetGroupInfoByGroupId(l.ctx, groupId) + if err != nil { + return nil, err + } + + _, err = l.svc.GetPersonByMemberIdAndGroupId(l.ctx, personId, groupId) + if err != nil { + return nil, err + } + + groupMembers, err := l.svc.GetMembersByGroupId(groupId) + if err != nil { + return nil, err + } + + return &pb.GetGroupMemberListResp{ + Members: NewRPCGroupMemberInfos(groupMembers), + }, nil +} diff --git a/service/group/logic/get_mute_list.go b/service/group/logic/get_mute_list.go new file mode 100644 index 0000000..44994c1 --- /dev/null +++ b/service/group/logic/get_mute_list.go @@ -0,0 +1,47 @@ +package logic + +import ( + "context" + pb "gitlab.33.cn/chat/dtalk/service/group/api" + "gitlab.33.cn/chat/dtalk/service/group/service" +) + +type GetMuteListLogic struct { + ctx context.Context + svc *service.Service +} + +func NewGetMuteListLogic(ctx context.Context, svc *service.Service) *GetMuteListLogic { + return &GetMuteListLogic{ + ctx: ctx, + svc: svc, + } +} + +// GetMuteList 查询群禁言列表 +func (l *GetMuteListLogic) GetMuteList(req *pb.GetMuteListReq) (*pb.GetMuteListResp, error) { + groupId := req.GroupId + personId := req.PersonId + + _, err := l.svc.GetGroupInfoByGroupId(l.ctx, groupId) + if err != nil { + return nil, err + } + + person, err := l.svc.GetPersonByMemberIdAndGroupId(l.ctx, personId, groupId) + if err != nil { + return nil, err + } + if err = person.IsAdmin(); err != nil { + return nil, err + } + + muteList, err := l.svc.GetGroupMembersMutedByGroupId(groupId) + if err != nil { + return nil, err + } + + return &pb.GetMuteListResp{ + Members: NewRPCGroupMemberInfos(muteList), + }, nil +} diff --git a/service/group/logic/get_pri_group_info.go b/service/group/logic/get_pri_group_info.go new file mode 100644 index 0000000..aa5f0c4 --- /dev/null +++ b/service/group/logic/get_pri_group_info.go @@ -0,0 +1,51 @@ +package logic + +import ( + "context" + pb "gitlab.33.cn/chat/dtalk/service/group/api" + "gitlab.33.cn/chat/dtalk/service/group/service" +) + +type GetPriGroupInfoLogic struct { + ctx context.Context + svc *service.Service +} + +func NewGetPriGroupInfoLogic(ctx context.Context, svc *service.Service) *GetPriGroupInfoLogic { + return &GetPriGroupInfoLogic{ + ctx: ctx, + svc: svc, + } +} + +// GetPriGroupInfo 查询群全部信息 +func (l *GetPriGroupInfoLogic) GetPriGroupInfo(req *pb.GetPriGroupInfoReq) (*pb.GetPriGroupInfoResp, error) { + group, err := l.svc.GetGroupInfoByGroupId(l.ctx, req.GroupId) + if err != nil { + return nil, err + } + + owner, err := l.svc.GetMemberByMemberIdAndGroupId(l.ctx, group.GroupOwnerId, group.GroupId) + if err != nil { + return nil, err + } + + person, err := l.svc.GetPersonByMemberIdAndGroupId(l.ctx, req.PersonId, req.GroupId) + if err != nil { + return nil, err + } + + members, err := l.svc.GetGroupMembersByGroupIdWithLimit(group.GroupId, 0, int64(req.DisplayNum)) + if err != nil { + return nil, err + } + + groupInfo := NewRPCGroupInfo(group) + groupInfo.Owner = NewRPCGroupMemberInfo(owner) + groupInfo.Person = NewRPCGroupMemberInfo(person) + groupInfo.Members = NewRPCGroupMemberInfos(members) + + return &pb.GetPriGroupInfoResp{ + Group: groupInfo, + }, nil +} diff --git a/service/group/logic/get_pub_group_info.go b/service/group/logic/get_pub_group_info.go new file mode 100644 index 0000000..e18fc2a --- /dev/null +++ b/service/group/logic/get_pub_group_info.go @@ -0,0 +1,53 @@ +package logic + +import ( + "context" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + pb "gitlab.33.cn/chat/dtalk/service/group/api" + "gitlab.33.cn/chat/dtalk/service/group/service" +) + +type GetPubGroupInfoLogic struct { + ctx context.Context + svc *service.Service +} + +func NewGetPubGroupInfoLogic(ctx context.Context, svc *service.Service) *GetPubGroupInfoLogic { + return &GetPubGroupInfoLogic{ + ctx: ctx, + svc: svc, + } +} + +// GetPubGroupInfo 查询群公开信息 +func (l *GetPubGroupInfoLogic) GetPubGroupInfo(req *pb.GetPubGroupInfoReq) (*pb.GetPubGroupInfoResp, error) { + group, err := l.svc.GetGroupInfoByGroupId(l.ctx, req.GroupId) + if err != nil { + return nil, err + } + + owner, err := l.svc.GetMemberByMemberIdAndGroupId(l.ctx, group.GroupOwnerId, group.GroupId) + if err != nil { + return nil, err + } + + groupInfo := NewRPCGroupInfo(group) + groupInfo.Owner = NewRPCGroupMemberInfo(owner) + + person, err := l.svc.GetPersonByMemberIdAndGroupId(l.ctx, req.PersonId, req.GroupId) + if err != nil { + if xerror.NewError(xerror.GroupPersonNotExist).Error() != err.Error() { + return nil, err + } + } + + if person == nil { + groupInfo.AESKey = "" + } else { + groupInfo.Person = NewRPCGroupMemberInfo(person) + } + + return &pb.GetPubGroupInfoResp{ + Group: groupInfo, + }, nil +} diff --git a/service/group/logic/group_disband.go b/service/group/logic/group_disband.go new file mode 100644 index 0000000..93c65f9 --- /dev/null +++ b/service/group/logic/group_disband.go @@ -0,0 +1,42 @@ +package logic + +import ( + "context" + pb "gitlab.33.cn/chat/dtalk/service/group/api" + "gitlab.33.cn/chat/dtalk/service/group/service" +) + +type GroupDisbandLogic struct { + ctx context.Context + svc *service.Service +} + +func NewGroupDisbandLogic(ctx context.Context, svc *service.Service) *GroupDisbandLogic { + return &GroupDisbandLogic{ + ctx: ctx, + svc: svc, + } +} + +// GroupDisband 解散群 +func (l *GroupDisbandLogic) GroupDisband(req *pb.GroupDisbandReq) (*pb.GroupDisbandResp, error) { + groupId := req.GroupId + personId := req.PersonId + + person, err := l.svc.GetPersonByMemberIdAndGroupId(l.ctx, personId, groupId) + if err != nil { + return nil, err + } + + // 只有群主可以解散群 + if err = person.IsOwner(); err != nil { + return nil, err + } + + err = l.svc.GroupDisband(l.ctx, groupId, personId) + if err != nil { + return nil, err + } + + return &pb.GroupDisbandResp{}, nil +} diff --git a/service/group/logic/group_exit.go b/service/group/logic/group_exit.go new file mode 100644 index 0000000..250e723 --- /dev/null +++ b/service/group/logic/group_exit.go @@ -0,0 +1,47 @@ +package logic + +import ( + "context" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + pb "gitlab.33.cn/chat/dtalk/service/group/api" + "gitlab.33.cn/chat/dtalk/service/group/service" +) + +type GroupExitLogic struct { + ctx context.Context + svc *service.Service +} + +func NewGroupExitLogic(ctx context.Context, svc *service.Service) *GroupExitLogic { + return &GroupExitLogic{ + ctx: ctx, + svc: svc, + } +} + +// GroupExit 退出群 +func (l *GroupExitLogic) GroupExit(req *pb.GroupExitReq) (*pb.GroupExitResp, error) { + groupId := req.GroupId + personId := req.PersonId + + group, err := l.svc.GetGroupInfoByGroupId(l.ctx, groupId) + if err != nil { + return nil, err + } + + person, err := l.svc.GetPersonByMemberIdAndGroupId(l.ctx, personId, groupId) + if err != nil { + return nil, err + } + + if err = person.IsOwner(); err == nil { + return nil, xerror.NewError(xerror.GroupOwnerExit) + } + + err = l.svc.ExitGroup(l.ctx, group, person) + if err != nil { + return nil, err + } + + return &pb.GroupExitResp{}, nil +} diff --git a/service/group/logic/group_remove.go b/service/group/logic/group_remove.go new file mode 100644 index 0000000..df42642 --- /dev/null +++ b/service/group/logic/group_remove.go @@ -0,0 +1,69 @@ +package logic + +import ( + "context" + pb "gitlab.33.cn/chat/dtalk/service/group/api" + "gitlab.33.cn/chat/dtalk/service/group/model/biz" + "gitlab.33.cn/chat/dtalk/service/group/service" +) + +type GroupRemoveLogic struct { + ctx context.Context + svc *service.Service +} + +func NewGroupRemoveLogic(ctx context.Context, svc *service.Service) *GroupRemoveLogic { + return &GroupRemoveLogic{ + ctx: ctx, + svc: svc, + } +} + +// GroupRemove 踢人 +func (l *GroupRemoveLogic) GroupRemove(req *pb.GroupRemoveReq) (*pb.GroupRemoveResp, error) { + groupId := req.GroupId + memberIds := req.MemberIds + personId := req.PersonId + + // 判断一下该群是否存在 + group, err := l.svc.GetGroupInfoByGroupId(l.ctx, groupId) + if err != nil { + return nil, err + } + + // 判断踢人者权限 + person, err := l.svc.GetPersonByMemberIdAndGroupId(l.ctx, personId, groupId) + if err != nil { + return nil, err + } + if err = person.IsAdmin(); err != nil { + return nil, err + } + + // 过滤可以踢人的列表 + needDeleteMembers := l.svc.GetFilteredGroupMembers(l.ctx, group, FilteredMemberIds(memberIds)) + if len(needDeleteMembers) == 0 { + return &pb.GroupRemoveResp{MemberNum: group.GroupMemberNum}, nil + } + + canRemoveMemberIds := make([]string, 0) + canRemoveMembers := make([]*biz.GroupMember, 0) + for _, member := range needDeleteMembers { + if err := person.RemoveOneMember(member); err != nil { + return nil, err + } + + canRemoveMemberIds = append(canRemoveMemberIds, member.GroupMemberId) + canRemoveMembers = append(canRemoveMembers, member) + } + + // 执行踢人 + if err = l.svc.RemoveGroupMembers(l.ctx, group, canRemoveMembers); err != nil { + return nil, err + } + + return &pb.GroupRemoveResp{ + MemberNum: group.GroupMemberNum - int32(len(canRemoveMemberIds)), + MemberIds: canRemoveMemberIds, + }, nil +} diff --git a/service/group/logic/http/changeowner.go b/service/group/logic/http/changeowner.go new file mode 100644 index 0000000..17c2081 --- /dev/null +++ b/service/group/logic/http/changeowner.go @@ -0,0 +1,60 @@ +package logic + +import ( + "context" + "github.com/rs/zerolog" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/service/group/model/types" + "gitlab.33.cn/chat/dtalk/service/group/service" +) + +type ChangeOwnerLogic struct { + ctx context.Context + svc *service.Service + log zerolog.Logger +} + +func NewChangeOwnerLogic(ctx context.Context, svc *service.Service) *ChangeOwnerLogic { + return &ChangeOwnerLogic{ + ctx: ctx, + svc: svc, + log: svc.GetLog(), + } +} + +func (l *ChangeOwnerLogic) ChangeOwner(req *types.ChangeOwnerRequest) (res *types.ChangeOwnerResponse, err error) { + groupId := req.Id + personId := req.PersonId + memberId := req.MemberId + + group, err := l.svc.GetGroupInfoByGroupId(l.ctx, groupId) + if err != nil { + return nil, err + } + + person, err := l.svc.GetPersonByMemberIdAndGroupId(l.ctx, personId, groupId) + if err != nil { + return nil, err + } + + member, err := l.svc.GetMemberByMemberIdAndGroupId(l.ctx, memberId, groupId) + if err != nil { + return nil, err + } + + // 只有群主可以转让群 + if err = person.IsOwner(); err != nil { + return nil, err + } + + if personId == memberId { + return nil, xerror.NewError(xerror.GroupChangeOwnerSelf) + } + + err = l.svc.ChangeOwner(l.ctx, group, person, member) + if err != nil { + return nil, err + } + + return res, nil +} diff --git a/service/group/logic/invite_group_members.go b/service/group/logic/invite_group_members.go new file mode 100644 index 0000000..d849e0b --- /dev/null +++ b/service/group/logic/invite_group_members.go @@ -0,0 +1,68 @@ +package logic + +import ( + "context" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + pb "gitlab.33.cn/chat/dtalk/service/group/api" + "gitlab.33.cn/chat/dtalk/service/group/model/biz" + "gitlab.33.cn/chat/dtalk/service/group/service" +) + +type InviteGroupMembersLogic struct { + ctx context.Context + svc *service.Service +} + +func NewInviteGroupMembersLogic(ctx context.Context, svc *service.Service) *InviteGroupMembersLogic { + return &InviteGroupMembersLogic{ + ctx: ctx, + svc: svc, + } +} + +// InviteGroupMembers 邀请新成员 +func (l *InviteGroupMembersLogic) InviteGroupMembers(req *pb.InviteGroupMembersReq) (*pb.InviteGroupMembersResp, error) { + //personId := req.Inviter.MemberId + groupId := req.GroupId + inviterId := req.InviterId + newMemberIds := FilteredMemberIds(req.MemberIds) + + group, err := l.svc.GetGroupInfoByGroupId(l.ctx, groupId) + if err != nil { + return nil, err + } + + if inviterId == "" { + switch group.GroupJoinType { + case biz.GroupJoinTypeAny: + err = l.svc.InviteMembers(l.ctx, group, newMemberIds) + if err != nil { + return nil, err + } + return &pb.InviteGroupMembersResp{}, nil + default: + return nil, xerror.NewError(xerror.GroupInvitePermissionDenied) + } + } + + // 得到邀请人信息 + inviter, err := l.svc.GetPersonByMemberIdAndGroupId(l.ctx, inviterId, groupId) + if err != nil { + return nil, err + } + + switch inviter.TryInvite(group) { + case biz.InviteOk: + err = l.svc.InviteMembers(l.ctx, group, newMemberIds) + if err != nil { + return nil, err + } + return &pb.InviteGroupMembersResp{}, nil + case biz.InviteApply: + return nil, xerror.NewError(xerror.GroupInvitePermissionDenied) + case biz.InviteFail: + return nil, xerror.NewError(xerror.GroupInvitePermissionDenied) + default: + return nil, xerror.NewError(xerror.GroupInvitePermissionDenied) + } +} diff --git a/service/group/logic/set_admin.go b/service/group/logic/set_admin.go new file mode 100644 index 0000000..2669cbf --- /dev/null +++ b/service/group/logic/set_admin.go @@ -0,0 +1,60 @@ +package logic + +import ( + "context" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + pb "gitlab.33.cn/chat/dtalk/service/group/api" + "gitlab.33.cn/chat/dtalk/service/group/model/biz" + "gitlab.33.cn/chat/dtalk/service/group/service" +) + +type SetAdminLogic struct { + ctx context.Context + svc *service.Service +} + +func NewSetAdminLogic(ctx context.Context, svc *service.Service) *SetAdminLogic { + return &SetAdminLogic{ + ctx: ctx, + svc: svc, + } +} + +// SetAdmin 设置管理员 +func (l *SetAdminLogic) SetAdmin(req *pb.SetAdminReq) (*pb.SetAdminResp, error) { + groupId := req.GroupId + personId := req.PersonId + memberId := req.MemberId + memberType := int32(req.GroupMemberType) + + group, err := l.svc.GetGroupInfoByGroupId(l.ctx, groupId) + if err != nil { + return nil, err + } + + person, err := l.svc.GetPersonByMemberIdAndGroupId(l.ctx, personId, groupId) + if err != nil { + return nil, err + } + // 只有群主可以设置管理员 + if personId != group.GroupOwnerId || person.GroupMemberType != biz.GroupMemberTypeOwner || memberId == personId { + err = xerror.NewError(xerror.GroupOwnerSetAdmin) + return nil, err + } + + member, err := l.svc.GetMemberByMemberIdAndGroupId(l.ctx, memberId, groupId) + if err != nil { + return nil, err + } + + if err = group.TrySetAdmin(); memberType == biz.GroupMemberTypeAdmin && err != nil { + return nil, err + } + + err = l.svc.SetAdmin(l.ctx, group, member, memberType) + if err != nil { + return nil, err + } + + return &pb.SetAdminResp{}, nil +} diff --git a/service/group/logic/update_group_avatar.go b/service/group/logic/update_group_avatar.go new file mode 100644 index 0000000..accd863 --- /dev/null +++ b/service/group/logic/update_group_avatar.go @@ -0,0 +1,45 @@ +package logic + +import ( + "context" + pb "gitlab.33.cn/chat/dtalk/service/group/api" + "gitlab.33.cn/chat/dtalk/service/group/service" +) + +type UpdateGroupAvatarLogic struct { + ctx context.Context + svc *service.Service +} + +func NewUpdateGroupAvatarLogic(ctx context.Context, svc *service.Service) *UpdateGroupAvatarLogic { + return &UpdateGroupAvatarLogic{ + ctx: ctx, + svc: svc, + } +} + +// UpdateGroupAvatar 更新群头像 +func (l *UpdateGroupAvatarLogic) UpdateGroupAvatar(req *pb.UpdateGroupAvatarReq) (*pb.UpdateGroupAvatarResp, error) { + groupId := req.GroupId + personId := req.PersonId + groupAvatar := req.Avatar + + group, err := l.svc.GetGroupInfoByGroupId(l.ctx, groupId) + if err != nil { + return nil, err + } + + person, err := l.svc.GetPersonByMemberIdAndGroupId(l.ctx, personId, groupId) + if err != nil { + return nil, err + } + if err = person.IsAdmin(); err != nil { + return nil, err + } + + if err := l.svc.UpdateGroupAvatar(l.ctx, group, groupAvatar); err != nil { + return nil, err + } + + return &pb.UpdateGroupAvatarResp{}, nil +} diff --git a/service/group/logic/update_group_friend_type.go b/service/group/logic/update_group_friend_type.go new file mode 100644 index 0000000..2c65bb3 --- /dev/null +++ b/service/group/logic/update_group_friend_type.go @@ -0,0 +1,44 @@ +package logic + +import ( + "context" + pb "gitlab.33.cn/chat/dtalk/service/group/api" + "gitlab.33.cn/chat/dtalk/service/group/service" +) + +type UpdateGroupFriendTypeLogic struct { + ctx context.Context + svc *service.Service +} + +func NewUpdateGroupFriendTypeLogic(ctx context.Context, svc *service.Service) *UpdateGroupFriendTypeLogic { + return &UpdateGroupFriendTypeLogic{ + ctx: ctx, + svc: svc, + } +} + +// UpdateGroupFriendType 更新群内加好友设置 +func (l *UpdateGroupFriendTypeLogic) UpdateGroupFriendType(req *pb.UpdateGroupFriendTypeReq) (*pb.UpdateGroupFriendTypeResp, error) { + groupId := req.GroupId + personId := req.PersonId + friendType := int32(req.GroupFriendType) + + group, err := l.svc.GetGroupInfoByGroupId(l.ctx, groupId) + if err != nil { + return nil, err + } + + person, err := l.svc.GetPersonByMemberIdAndGroupId(l.ctx, personId, groupId) + if err != nil { + return nil, err + } + if err = person.IsAdmin(); err != nil { + return nil, err + } + + if err := l.svc.UpdateGroupFriendType(l.ctx, group, friendType); err != nil { + return nil, err + } + return &pb.UpdateGroupFriendTypeResp{}, nil +} diff --git a/service/group/logic/update_group_join_type.go b/service/group/logic/update_group_join_type.go new file mode 100644 index 0000000..3bfbb1a --- /dev/null +++ b/service/group/logic/update_group_join_type.go @@ -0,0 +1,45 @@ +package logic + +import ( + "context" + pb "gitlab.33.cn/chat/dtalk/service/group/api" + "gitlab.33.cn/chat/dtalk/service/group/service" +) + +type UpdateGroupJoinTypeLogic struct { + ctx context.Context + svc *service.Service +} + +func NewUpdateGroupJoinTypeLogic(ctx context.Context, svc *service.Service) *UpdateGroupJoinTypeLogic { + return &UpdateGroupJoinTypeLogic{ + ctx: ctx, + svc: svc, + } +} + +// UpdateGroupJoinType 更新加群设置 +func (l *UpdateGroupJoinTypeLogic) UpdateGroupJoinType(req *pb.UpdateGroupJoinTypeReq) (*pb.UpdateGroupJoinTypeResp, error) { + groupId := req.GroupId + personId := req.PersonId + joinType := int32(req.GroupJoinType) + + group, err := l.svc.GetGroupInfoByGroupId(l.ctx, groupId) + if err != nil { + return nil, err + } + + person, err := l.svc.GetPersonByMemberIdAndGroupId(l.ctx, personId, groupId) + if err != nil { + return nil, err + } + if err = person.IsAdmin(); err != nil { + return nil, err + } + + if err := l.svc.UpdateGroupJoinType(l.ctx, group, joinType); err != nil { + return nil, err + } + + return &pb.UpdateGroupJoinTypeResp{}, nil +} diff --git a/service/group/logic/update_group_member_mute_time.go b/service/group/logic/update_group_member_mute_time.go new file mode 100644 index 0000000..dde8964 --- /dev/null +++ b/service/group/logic/update_group_member_mute_time.go @@ -0,0 +1,65 @@ +package logic + +import ( + "context" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + pb "gitlab.33.cn/chat/dtalk/service/group/api" + "gitlab.33.cn/chat/dtalk/service/group/model/biz" + "gitlab.33.cn/chat/dtalk/service/group/service" +) + +type UpdateGroupMemberMuteTimeLogic struct { + ctx context.Context + svc *service.Service +} + +func NewUpdateGroupMemberMuteTimeLogic(ctx context.Context, svc *service.Service) *UpdateGroupMemberMuteTimeLogic { + return &UpdateGroupMemberMuteTimeLogic{ + ctx: ctx, + svc: svc, + } +} + +// UpdateGroupMemberMuteTime 更新群成员禁言时间 +func (l *UpdateGroupMemberMuteTimeLogic) UpdateGroupMemberMuteTime(req *pb.UpdateGroupMemberMuteTimeReq) (*pb.UpdateGroupMemberMuteTimeResp, error) { + groupId := req.GroupId + memberIds := req.MemberIds + personId := req.PersonId + muteTime := req.MuteTime + var members []*biz.GroupMember + + group, err := l.svc.GetGroupInfoByGroupId(l.ctx, groupId) + if err != nil { + return nil, err + } + + person, err := l.svc.GetPersonByMemberIdAndGroupId(l.ctx, personId, groupId) + if err != nil { + return nil, err + } + if err = person.IsAdmin(); err != nil { + return nil, err + } + + // 过滤 + for _, memberId := range memberIds { + member, err := l.svc.GetMemberByMemberIdAndGroupId(l.ctx, memberId, groupId) + if err != nil { + return nil, err + } + if err := member.IsAdmin(); err == nil { + return nil, xerror.NewError(xerror.GroupMutePermission) + } + + members = append(members, member) + } + + members, err = l.svc.UpdateMembersMuteTime(l.ctx, group, members, muteTime) + if err != nil { + return nil, err + } + + return &pb.UpdateGroupMemberMuteTimeResp{ + Members: NewRPCGroupMemberInfos(members), + }, nil +} diff --git a/service/group/logic/update_group_member_name.go b/service/group/logic/update_group_member_name.go new file mode 100644 index 0000000..abd5d94 --- /dev/null +++ b/service/group/logic/update_group_member_name.go @@ -0,0 +1,42 @@ +package logic + +import ( + "context" + pb "gitlab.33.cn/chat/dtalk/service/group/api" + "gitlab.33.cn/chat/dtalk/service/group/service" +) + +type UpdateGroupMemberNameLogic struct { + ctx context.Context + svc *service.Service +} + +func NewUpdateGroupMemberNameLogic(ctx context.Context, svc *service.Service) *UpdateGroupMemberNameLogic { + return &UpdateGroupMemberNameLogic{ + ctx: ctx, + svc: svc, + } +} + +// UpdateGroupMemberName 更新群成员群昵称 +func (l *UpdateGroupMemberNameLogic) UpdateGroupMemberName(req *pb.UpdateGroupMemberNameReq) (*pb.UpdateGroupMemberNameResp, error) { + groupId := req.GroupId + personId := req.PersonId + memberName := req.MemberName + + group, err := l.svc.GetGroupInfoByGroupId(l.ctx, groupId) + if err != nil { + return nil, err + } + + person, err := l.svc.GetPersonByMemberIdAndGroupId(l.ctx, personId, groupId) + if err != nil { + return nil, err + } + + if err := l.svc.UpdateMemberName(l.ctx, group, person, memberName); err != nil { + return nil, err + } + + return &pb.UpdateGroupMemberNameResp{}, nil +} diff --git a/service/group/logic/update_group_mute_type.go b/service/group/logic/update_group_mute_type.go new file mode 100644 index 0000000..8af636b --- /dev/null +++ b/service/group/logic/update_group_mute_type.go @@ -0,0 +1,45 @@ +package logic + +import ( + "context" + pb "gitlab.33.cn/chat/dtalk/service/group/api" + "gitlab.33.cn/chat/dtalk/service/group/service" +) + +type UpdateGroupMuteTypeLogic struct { + ctx context.Context + svc *service.Service +} + +func NewUpdateGroupMuteTypeLogic(ctx context.Context, svc *service.Service) *UpdateGroupMuteTypeLogic { + return &UpdateGroupMuteTypeLogic{ + ctx: ctx, + svc: svc, + } +} + +// UpdateGroupMuteType 更新群内禁言设置 +func (l *UpdateGroupMuteTypeLogic) UpdateGroupMuteType(req *pb.UpdateGroupMuteTypeReq) (*pb.UpdateGroupMuteTypeResp, error) { + groupId := req.GroupId + personId := req.PersonId + muteType := int32(req.GroupMuteType) + + group, err := l.svc.GetGroupInfoByGroupId(l.ctx, groupId) + if err != nil { + return nil, err + } + + person, err := l.svc.GetPersonByMemberIdAndGroupId(l.ctx, personId, groupId) + if err != nil { + return nil, err + } + if err = person.IsAdmin(); err != nil { + return nil, err + } + + if err := l.svc.UpdateGroupMuteType(l.ctx, group, muteType); err != nil { + return nil, err + } + + return &pb.UpdateGroupMuteTypeResp{}, nil +} diff --git a/service/group/logic/update_group_name.go b/service/group/logic/update_group_name.go new file mode 100644 index 0000000..3a73c94 --- /dev/null +++ b/service/group/logic/update_group_name.go @@ -0,0 +1,46 @@ +package logic + +import ( + "context" + pb "gitlab.33.cn/chat/dtalk/service/group/api" + "gitlab.33.cn/chat/dtalk/service/group/service" +) + +type UpdateGroupNameLogic struct { + ctx context.Context + svc *service.Service +} + +func NewUpdateGroupNameLogic(ctx context.Context, svc *service.Service) *UpdateGroupNameLogic { + return &UpdateGroupNameLogic{ + ctx: ctx, + svc: svc, + } +} + +// UpdateGroupName 更新群名称 +func (l *UpdateGroupNameLogic) UpdateGroupName(req *pb.UpdateGroupNameReq) (*pb.UpdateGroupNameResp, error) { + groupId := req.GroupId + personId := req.PersonId + groupName := req.Name + groupPubName := req.PublicName + + group, err := l.svc.GetGroupInfoByGroupId(l.ctx, groupId) + if err != nil { + return nil, err + } + + person, err := l.svc.GetPersonByMemberIdAndGroupId(l.ctx, personId, groupId) + if err != nil { + return nil, err + } + if err = person.IsAdmin(); err != nil { + return nil, err + } + + if err := l.svc.UpdateGroupName(l.ctx, group, groupName, groupPubName); err != nil { + return nil, err + } + + return &pb.UpdateGroupNameResp{}, nil +} diff --git a/service/group/logic/util.go b/service/group/logic/util.go new file mode 100644 index 0000000..baaf3c0 --- /dev/null +++ b/service/group/logic/util.go @@ -0,0 +1,94 @@ +package logic + +import ( + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + pb "gitlab.33.cn/chat/dtalk/service/group/api" + "gitlab.33.cn/chat/dtalk/service/group/model/biz" + "strings" +) + +func FilteredMemberIds(memberIds []string) []string { + newMemberIds := make([]string, 0, len(memberIds)) + for _, memberId := range memberIds { + memberId, err := FilteredMemberId(memberId) + if err != nil { + continue + } + + newMemberIds = append(newMemberIds, memberId) + } + + return newMemberIds +} + +func FilteredMemberId(memberId string) (string, error) { + memberId = strings.TrimSpace(memberId) + if memberId != "" && len(memberId) < 40 { + return memberId, nil + } + return "", xerror.NewError(xerror.ParamsError) +} + +func NewRPCGroupInfo(do *biz.GroupInfo) *pb.GroupBizInfo { + if do == nil { + return &pb.GroupBizInfo{} + } + + return &pb.GroupBizInfo{ + Id: do.GroupId, + MarkId: do.GroupMarkId, + Name: do.GroupName, + Avatar: do.GroupAvatar, + MemberNum: do.GroupMemberNum, + MemberMaximum: do.GroupMaximum, + Introduce: do.GroupIntroduce, + Status: pb.GroupStatus(do.GroupStatus), + OwnerId: do.GroupOwnerId, + CreateTime: do.GroupCreateTime, + UpdateTime: do.GroupUpdateTime, + JoinType: pb.GroupJoinType(do.GroupJoinType), + MuteType: pb.GroupMuteType(do.GroupMuteType), + FriendType: pb.GroupFriendType(do.GroupFriendType), + MuteNum: do.MuteNum, + AdminNum: do.AdminNum, + AESKey: do.AESKey, + PubName: do.GroupPubName, + Type: pb.GroupType(do.GroupType), + Owner: nil, + Person: nil, + Members: nil, + } +} + +func NewRPCGroupInfos(dos []*biz.GroupInfo) []*pb.GroupBizInfo { + dtos := make([]*pb.GroupBizInfo, 0, len(dos)) + for _, do := range dos { + dtos = append(dtos, NewRPCGroupInfo(do)) + } + + return dtos +} + +func NewRPCGroupMemberInfo(do *biz.GroupMember) *pb.GroupMemberBizInfo { + if do == nil { + return &pb.GroupMemberBizInfo{} + } + + return &pb.GroupMemberBizInfo{ + GroupId: do.GroupId, + Id: do.GroupMemberId, + Name: do.GroupMemberName, + Type: pb.GroupMemberType(do.GroupMemberType), + MuteTime: do.GroupMemberMuteTime, + JoinTime: do.GroupMemberJoinTime, + } +} + +func NewRPCGroupMemberInfos(dos []*biz.GroupMember) []*pb.GroupMemberBizInfo { + dtos := make([]*pb.GroupMemberBizInfo, 0, len(dos)) + for _, do := range dos { + dtos = append(dtos, NewRPCGroupMemberInfo(do)) + } + + return dtos +} diff --git a/service/group/model/biz/const.go b/service/group/model/biz/const.go new file mode 100644 index 0000000..d0ae040 --- /dev/null +++ b/service/group/model/biz/const.go @@ -0,0 +1,12 @@ +package biz + +const ( + DisPlayNum = 10 // 查看群信息默认显示的人数 + + MuteMaximum = int64(^uint(0) >> 1) // 永久禁言的时间 9223372036854775807 +) + +var ( + GroupMaximum int32 = 2000 + AdminNum int32 = 10 +) diff --git a/service/group/model/biz/groupapply.go b/service/group/model/biz/groupapply.go new file mode 100644 index 0000000..99dd627 --- /dev/null +++ b/service/group/model/biz/groupapply.go @@ -0,0 +1,61 @@ +package biz + +import ( + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/service/group/model/types" + "gitlab.33.cn/utils/go-kit/convert" +) + +const ( + GroupApplyWait = 0 + GroupApplyAccept = 1 + GroupApplyReject = 2 + GroupApplyIgnore = 10 +) + +type GroupApplyBiz struct { + // 审批 ID + ApplyId int64 + // 群 ID + GroupId int64 + // 邀请人 ID, 空表示是自己主动申请的 + InviterId string + // 申请加入人 ID + MemberId string + // 申请备注 + ApplyNote string + // 审批人 ID + OperatorId string + // 审批情况 0=待审批, 1=审批通过, 2=审批不通过, 10=审批忽略 + ApplyStatus int32 + // 拒绝原因 + RejectReason string + // 创建时间 ms + CreateTime int64 + // 修改时间 ms + UpdateTime int64 +} + +// ToTypes 将业务模型转换为 api 展示信息 +func (g *GroupApplyBiz) ToTypes() *types.GroupApplyInfo { + return &types.GroupApplyInfo{ + ApplyId: convert.ToString(g.ApplyId), + GroupId: convert.ToString(g.GroupId), + InviterId: g.InviterId, + MemberId: g.MemberId, + ApplyNote: g.ApplyNote, + OperatorId: g.OperatorId, + ApplyStatus: g.ApplyStatus, + RejectReason: g.RejectReason, + CreateTime: g.CreateTime, + UpdateTime: g.UpdateTime, + } +} + +// IsWait 判断该审批是否被处理过 +func (g *GroupApplyBiz) IsWait() error { + if g.ApplyStatus == GroupApplyWait { + return nil + } + return xerror.NewError(xerror.GroupApplyUsed) +} diff --git a/service/group/model/biz/groupinfo.go b/service/group/model/biz/groupinfo.go new file mode 100644 index 0000000..fb01303 --- /dev/null +++ b/service/group/model/biz/groupinfo.go @@ -0,0 +1,145 @@ +package biz + +import ( + "context" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/service/group/model/types" + "gitlab.33.cn/utils/go-kit/convert" +) + +type groupContextKey string + +const ( + // GroupContextKey . + GroupContextKey = groupContextKey("FZM-Group-Token") + GroupIdContextKey = groupContextKey("FZM-Group-Id") +) + +const ( + GroupStatusNormal = 0 // 正常 + GroupStatusBlock = 1 // 封禁 + GroupStatusDisBand = 2 // 解散 + + GroupJoinTypeAny = 0 // 无需审批(默认) + GroupJoinTypeAdmin = 1 // 禁止加群,群主和管理员邀请加群 + GroupJoinTypeApply = 2 // 普通人邀请需要审批,群主和管理员直接加群 + + GroupMuteTypeAny = 0 // 全员可发言 + GroupMuteTypeAdmin = 1 // 全员禁言(除群主和管理员) + + GroupFriendTypeAllow = 0 // 群内可加好友 + GroupFriendTypeDeny = 1 // 群内禁止加好友 + + // 群类型 + GroupTypeNormal = 0 // 普通群 + GroupTypeEnt = 1 // 全员群 + GroupTypeDep = 2 // 部门群 +) + +type GroupInfo struct { + GroupId int64 + GroupMarkId string + GroupName string + GroupAvatar string + // 群人数 + GroupMemberNum int32 + // 群人数上限 + GroupMaximum int32 + GroupIntroduce string + // 群状态,0=正常 1=封禁 2=解散 + GroupStatus int32 + GroupOwnerId string + GroupCreateTime int64 + GroupUpdateTime int64 + // 加群方式,0=无需审批(默认),1=禁止加群,群主和管理员邀请加群, 2=普通人邀请需要审批,群主和管理员直接加群 + GroupJoinType int32 + // 禁言, 0=全员可发言, 1=全员禁言(除群主和管理员) + GroupMuteType int32 + // 加好友限制, 0=群内可加好友,1=群内禁止加好友 + GroupFriendType int32 + + // 群内当前被禁言的人数 + MuteNum int32 `json:"muteNum"` + // 群内管理员数量 + AdminNum int32 `json:"adminNum"` + + // + AESKey string + // + GroupPubName string + // 群类型 (0: 普通群, 1: 全员群, 2: 部门群) + GroupType int32 +} + +func (g *GroupInfo) IsNormal() error { + switch g.GroupStatus { + case GroupStatusNormal: + return nil + case GroupStatusBlock: + return xerror.NewError(xerror.GroupStatusBlock) + case GroupStatusDisBand: + return xerror.NewError(xerror.GroupStatusDisBand) + default: + return xerror.NewError(xerror.CodeInnerError) + } +} + +func (g *GroupInfo) ToTypes(owner *types.GroupMember, person *types.GroupMember) *types.GroupInfo { + return &types.GroupInfo{ + Id: g.GroupId, + IdStr: convert.ToString(g.GroupId), + MarkId: g.GroupMarkId, + Name: g.GroupName, + Avatar: g.GroupAvatar, + Introduce: g.GroupIntroduce, + Owner: owner, + Person: person, + MemberNum: g.GroupMemberNum, + Maximum: g.GroupMaximum, + Status: g.GroupStatus, + CreateTime: g.GroupCreateTime, + JoinType: g.GroupJoinType, + MuteType: g.GroupMuteType, + FriendType: g.GroupFriendType, + MuteNum: g.MuteNum, + AdminNum: g.AdminNum, + AESKey: g.AESKey, + PublicName: g.GroupPubName, + GroupType: g.GroupType, + } +} + +func (g *GroupInfo) TryJoin(add int32) error { + if add+g.GroupMemberNum > g.GroupMaximum { + return xerror.NewError(xerror.GroupMemberLimit) + } + return nil +} + +func (g *GroupInfo) TrySetAdmin() error { + if g.AdminNum+1 > AdminNum { + return xerror.NewError(xerror.GroupAdminNumLimit) + } + + return nil +} + +func (g *GroupInfo) WithContext(ctx context.Context) context.Context { + return context.WithValue(ctx, GroupContextKey, g) +} + +func NewGroupInfoFromContext(ctx context.Context) (*GroupInfo, error) { + if group, ok := ctx.Value(GroupContextKey).(*GroupInfo); ok { + return group, nil + } + + return nil, xerror.NewError(xerror.GroupNotExist) +} + +func NewGroupIdFromContext(ctx context.Context) (int64, error) { + if groupId, ok := ctx.Value(GroupIdContextKey).(int64); ok { + return groupId, nil + } + + return 0, xerror.NewError(xerror.GroupNotExist) +} diff --git a/service/group/model/biz/groupmember.go b/service/group/model/biz/groupmember.go new file mode 100644 index 0000000..03fd6a6 --- /dev/null +++ b/service/group/model/biz/groupmember.go @@ -0,0 +1,105 @@ +package biz + +import ( + "context" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/service/group/model/types" +) + +type groupMemberContextKey string + +const ( + // GroupMemberContextKey . + GroupMemberContextKey = groupMemberContextKey("FZM-Group-Member-Token") +) + +const ( + GroupMemberTypeOwner = 2 // 群主 + GroupMemberTypeAdmin = 1 // 管理员 + GroupMemberTypeNormal = 0 // 群员 + GroupMemberTypeOther = 10 // 退群 +) + +type GroupMember struct { + GroupId int64 + GroupMemberId string + GroupMemberName string + // 用户角色,0=群员,1=管理员, 2=群主,10=退群 + GroupMemberType int32 + // 该用户被禁言结束的时间 9223372036854775807=永久禁言 + GroupMemberMuteTime int64 + GroupMemberJoinTime int64 +} + +func (m *GroupMember) ToTypes() *types.GroupMember { + return &types.GroupMember{ + MemberId: m.GroupMemberId, + MemberName: m.GroupMemberName, + MemberType: m.GroupMemberType, + MemberMuteTime: m.GroupMemberMuteTime, + } +} + +func (m *GroupMember) IsAdmin() error { + if m.GroupMemberType < GroupMemberTypeAdmin { + return xerror.NewError(xerror.GroupAdminDeny) + } + return nil +} + +func (m *GroupMember) IsOwner() error { + if m.GroupMemberType < GroupMemberTypeOwner { + return xerror.NewError(xerror.GroupOwnerDeny) + } + return nil +} + +func (m *GroupMember) RemoveOneMember(member *GroupMember) error { + if m.GroupId != member.GroupId || m.GroupMemberType == GroupMemberTypeOther || member.GroupMemberType == GroupMemberTypeOwner { + return xerror.NewError(xerror.CodeInnerError) + } + + if m.GroupMemberType <= member.GroupMemberType { + return xerror.NewError(xerror.GroupHigherPermission) + } + + return nil +} + +type InviteFlow int + +const ( + InviteOk InviteFlow = 1 + InviteApply InviteFlow = 2 + InviteFail InviteFlow = 3 +) + +func (m *GroupMember) TryInvite(group *GroupInfo) InviteFlow { + if m.GroupMemberType == GroupMemberTypeNormal { + switch group.GroupJoinType { + case GroupJoinTypeApply: + return InviteApply + case GroupJoinTypeAny: + return InviteOk + case GroupJoinTypeAdmin: + return InviteFail + default: + return InviteFail + + } + } + + return InviteOk +} + +func (m *GroupMember) WithContext(ctx context.Context) context.Context { + return context.WithValue(ctx, GroupMemberContextKey, m) +} + +func NewGroupMemberFromContext(ctx context.Context) (*GroupMember, error) { + if groupMember, ok := ctx.Value(GroupMemberContextKey).(*GroupMember); ok { + return groupMember, nil + } + + return nil, xerror.NewError(xerror.GroupPersonNotExist) +} diff --git a/service/group/model/db/error.go b/service/group/model/db/error.go new file mode 100644 index 0000000..dd3376a --- /dev/null +++ b/service/group/model/db/error.go @@ -0,0 +1,8 @@ +package db + +import "errors" + +var ( + ErrGroupNotExist = errors.New("the group is not exist") + ErrGroupMemberNotExist = errors.New("the member is not exist") +) diff --git a/service/group/model/db/groupapply.go b/service/group/model/db/groupapply.go new file mode 100644 index 0000000..75c512c --- /dev/null +++ b/service/group/model/db/groupapply.go @@ -0,0 +1,59 @@ +package db + +import ( + "gitlab.33.cn/chat/dtalk/service/group/model/biz" + "gitlab.33.cn/utils/go-kit/convert" +) + +type GroupApply struct { + // 审批 ID + Id int64 + // 群 ID + GroupId int64 + // 邀请人 ID, 空表示是自己主动申请的 + InviterId string + // 申请加入人 ID + MemberId string + // 申请备注 + ApplyNote string + // 审批人 ID + OperatorId string + // 审批情况 0=待审批, 1=审批通过, 2=审批不通过, 10=审批忽略 + ApplyStatus int32 + // 拒绝原因 + RejectReason string + // 创建时间 ms + CreateTime int64 + // 修改时间 ms + UpdateTime int64 +} + +func ConvertGroupApply(res map[string]string) *GroupApply { + return &GroupApply{ + Id: convert.ToInt64(res["id"]), + GroupId: convert.ToInt64(res["group_id"]), + InviterId: convert.ToString(res["inviter_id"]), + MemberId: convert.ToString(res["member_id"]), + ApplyNote: convert.ToString(res["apply_note"]), + OperatorId: convert.ToString(res["operator_id"]), + ApplyStatus: convert.ToInt32(res["apply_status"]), + RejectReason: convert.ToString(res["reject_reason"]), + CreateTime: convert.ToInt64(res["create_time"]), + UpdateTime: convert.ToInt64(res["update_time"]), + } +} + +func (a *GroupApply) ToBiz() *biz.GroupApplyBiz { + return &biz.GroupApplyBiz{ + ApplyId: a.Id, + GroupId: a.GroupId, + InviterId: a.InviterId, + MemberId: a.MemberId, + ApplyNote: a.ApplyNote, + OperatorId: a.OperatorId, + ApplyStatus: a.ApplyStatus, + RejectReason: a.RejectReason, + CreateTime: a.CreateTime, + UpdateTime: a.UpdateTime, + } +} diff --git a/service/group/model/db/groupinfo.go b/service/group/model/db/groupinfo.go new file mode 100644 index 0000000..ee451a3 --- /dev/null +++ b/service/group/model/db/groupinfo.go @@ -0,0 +1,90 @@ +package db + +import ( + "gitlab.33.cn/chat/dtalk/service/group/model/biz" + "gitlab.33.cn/utils/go-kit/convert" +) + +type GroupInfo struct { + GroupId int64 `json:"groupId" form:"groupId"` + GroupMarkId string `json:"groupMarkId" form:"groupMarkId"` + GroupName string `json:"groupName" form:"groupName"` + GroupAvatar string `json:"groupAvatar" form:"groupAvatar"` + // 群人数 + GroupMemberNum int32 `json:"groupMemberNum" form:"groupMemberNum"` + // 群人数上限 + GroupMaximum int32 `json:"groupMaximum" form:"groupMaximum"` + GroupIntroduce string `json:"groupIntroduce" form:"groupIntroduce"` + // 群状态,0=正常 1=封禁 2=解散 + GroupStatus int32 `json:"groupStatus" form:"groupStatus"` + GroupOwnerId string `json:"groupOwnerId" form:"groupOwnerId"` + GroupCreateTime int64 `json:"groupCreateTime" form:"groupCreateTime"` + GroupUpdateTime int64 `json:"groupUpdateTime" form:"groupUpdateTime"` + // 加群方式,0=无需审批(默认),1=禁止加群,群主和管理员邀请加群, 2=普通人邀请需要审批,群主和管理员直接加群 + GroupJoinType int32 `json:"groupJoinType" form:"groupJoinType"` + // 禁言, 0=全员可发言, 1=全员禁言(除群主和管理员) + GroupMuteType int32 `json:"groupMuteType" form:"groupMuteType"` + // 加好友限制, 0=群内可加好友,1=群内禁止加好友 + GroupFriendType int32 + // + GroupAESKey string + // + GroupPubName string + // 群类型 (0: 普通群, 1: 全员群, 2: 部门群) + GroupType int32 +} + +func ConvertGroupInfos(maps []map[string]string) []*GroupInfo { + dtos := make([]*GroupInfo, 0, len(maps)) + for _, res := range maps { + dtos = append(dtos, ConvertGroupInfo(res)) + } + + return dtos +} + +func ConvertGroupInfo(res map[string]string) *GroupInfo { + return &GroupInfo{ + GroupId: convert.ToInt64(res["group_id"]), + GroupMarkId: res["group_mark_id"], + GroupName: res["group_name"], + GroupAvatar: res["group_avatar"], + GroupMemberNum: convert.ToInt32(res["group_member_num"]), + GroupMaximum: convert.ToInt32(res["group_maximum"]), + GroupIntroduce: res["group_Introduce"], + GroupStatus: convert.ToInt32(res["group_status"]), + GroupOwnerId: res["group_owner_id"], + GroupCreateTime: convert.ToInt64(res["group_create_time"]), + GroupUpdateTime: convert.ToInt64(res["group_update_time"]), + GroupJoinType: convert.ToInt32(res["group_join_type"]), + GroupMuteType: convert.ToInt32(res["group_mute_type"]), + GroupFriendType: convert.ToInt32(res["group_friend_type"]), + GroupAESKey: convert.ToString(res["group_aes_key"]), + GroupPubName: convert.ToString(res["group_pub_name"]), + GroupType: convert.ToInt32(res["group_type"]), + } +} + +func (g *GroupInfo) ToBiz() *biz.GroupInfo { + return &biz.GroupInfo{ + GroupId: g.GroupId, + GroupMarkId: g.GroupMarkId, + GroupName: g.GroupName, + GroupAvatar: g.GroupAvatar, + GroupMemberNum: g.GroupMemberNum, + GroupMaximum: g.GroupMaximum, + GroupIntroduce: g.GroupIntroduce, + GroupStatus: g.GroupStatus, + GroupOwnerId: g.GroupOwnerId, + GroupCreateTime: g.GroupCreateTime, + GroupUpdateTime: g.GroupUpdateTime, + GroupJoinType: g.GroupJoinType, + GroupMuteType: g.GroupMuteType, + GroupFriendType: g.GroupFriendType, + MuteNum: 0, + AdminNum: 0, + AESKey: g.GroupAESKey, + GroupPubName: g.GroupPubName, + GroupType: g.GroupType, + } +} diff --git a/service/group/model/db/groupmember.go b/service/group/model/db/groupmember.go new file mode 100644 index 0000000..0bd9c81 --- /dev/null +++ b/service/group/model/db/groupmember.go @@ -0,0 +1,89 @@ +package db + +import ( + "gitlab.33.cn/chat/dtalk/service/group/model/biz" + "gitlab.33.cn/utils/go-kit/convert" +) + +type GroupMember struct { + GroupId int64 `json:"groupId" form:"groupId"` + GroupMemberId string `json:"groupMemberId" form:"groupMemberId"` + GroupMemberName string `json:"groupMemberName" form:"groupMemberName"` + // 用户角色,2=群主,1=管理员,0=群员,10=退群 + GroupMemberType int32 `json:"groupMemberType" form:"groupMemberType"` + GroupMemberJoinTime int64 `json:"groupMemberJoinTime" form:"groupMemberJoinTime"` + GroupMemberUpdateTime int64 `json:"groupMemberUpdateTime" form:"groupMemberUpdateTime"` +} + +func ConvertGroupMember(res map[string]string) *GroupMember { + return &GroupMember{ + GroupId: convert.ToInt64(res["group_id"]), + GroupMemberId: res["group_member_id"], + GroupMemberName: res["group_member_name"], + GroupMemberType: convert.ToInt32(res["group_member_type"]), + GroupMemberJoinTime: convert.ToInt64(res["group_member_join_time"]), + //GroupMemberUpdateTime: convert.ToInt64(res["group_member_update_time"]), + } +} + +func (m *GroupMember) ToBiz() *biz.GroupMember { + return &biz.GroupMember{ + GroupId: m.GroupId, + GroupMemberId: m.GroupMemberId, + GroupMemberName: m.GroupMemberName, + GroupMemberType: m.GroupMemberType, + GroupMemberMuteTime: 0, + GroupMemberJoinTime: m.GroupMemberJoinTime, + } +} + +type GroupMemberMute struct { + GroupId int64 + GroupMemberId string + // 该用户被禁言结束的时间 9223372036854775807=永久禁言 + GroupMemberMuteTime int64 + GroupMemberMuteUpdateTime int64 +} + +func ConvertGroupMemberMute(res map[string]string) *GroupMemberMute { + return &GroupMemberMute{ + GroupId: convert.ToInt64(res["group_id"]), + GroupMemberId: res["group_member_id"], + GroupMemberMuteTime: convert.ToInt64(res["group_member_mute_time"]), + GroupMemberMuteUpdateTime: convert.ToInt64(res["group_member_mute_update_time"]), + } +} + +type GroupMemberWithMute struct { + GroupId int64 + GroupMemberId string + GroupMemberName string + GroupMemberType int32 + GroupMemberMuteTime int64 + GroupMemberJoinTime int64 +} + +func ConvertGroupMemberWithMute(res map[string]string) *GroupMemberWithMute { + if res["group_member_mute_time"] == "" { + res["group_member_mute_time"] = "0" + } + return &GroupMemberWithMute{ + GroupId: convert.ToInt64(res["group_id"]), + GroupMemberId: res["group_member_id"], + GroupMemberMuteTime: convert.ToInt64(res["group_member_mute_time"]), + GroupMemberName: res["group_member_name"], + GroupMemberType: convert.ToInt32(res["group_member_type"]), + GroupMemberJoinTime: convert.ToInt64(res["group_member_join_time"]), + } +} + +func (m *GroupMemberWithMute) ToBiz() *biz.GroupMember { + return &biz.GroupMember{ + GroupId: m.GroupId, + GroupMemberId: m.GroupMemberId, + GroupMemberName: m.GroupMemberName, + GroupMemberType: m.GroupMemberType, + GroupMemberMuteTime: m.GroupMemberMuteTime, + GroupMemberJoinTime: m.GroupMemberJoinTime, + } +} diff --git a/service/group/model/errors.go b/service/group/model/errors.go new file mode 100644 index 0000000..214db24 --- /dev/null +++ b/service/group/model/errors.go @@ -0,0 +1,17 @@ +package model + +import ( + "github.com/pkg/errors" +) + +var ( + //ErrAdminPermission = errors.New("需要更高的权限") + //ErrGroupIdNotExist = errors.New("该群号不存在") + ErrMemberNotExist = errors.New("该用户不在本群中") + //ErrPersonNotExist = errors.New("你已不在本群中") + ErrType = errors.New("参数不存在") + //ErrAdminNum = errors.New("管理员数量已满") + + ErrPushMsgArg = errors.New("rpc error: code = Unknown desc = rpc pushmsg arg error") + ErrRecordNotExist = errors.New("record not exist.") +) diff --git a/service/group/model/types/api.go b/service/group/model/types/api.go new file mode 100644 index 0000000..22ed152 --- /dev/null +++ b/service/group/model/types/api.go @@ -0,0 +1,433 @@ +package types + +type GeneralResponse struct { + Result int `json:"result"` + Message int `json:"message"` + Data interface{} `json:"data"` +} + +type GroupMember struct { + // 用户 ID + MemberId string `json:"memberId" form:"memberId"` + // 用户群昵称 + MemberName string `json:"memberName" form:"memberName"` + // 用户角色,2=群主,1=管理员,0=群员,10=退群 + MemberType int32 `json:"memberType" form:"memberType"` + // 该用户被禁言结束的时间 9223372036854775807=永久禁言 + MemberMuteTime int64 `json:"memberMuteTime"` +} + +type GroupInfo struct { + // 群 ID + Id int64 `json:"id" form:"id"` + IdStr string `json:"idStr"` + // 群显示的 ID + MarkId string `json:"markId" form:"markId"` + // 群名称 加密的 + Name string `json:"name" form:"name"` + // 公开的群名称 不加密的 + PublicName string `json:"publicName"` + // 头像 url + Avatar string `json:"avatar" form:"avatar"` + Introduce string `json:"introduce" form:"introduce"` + // 群主 信息 + Owner *GroupMember `json:"owner" form:"owner"` + // 本人在群内的信息 + Person *GroupMember `json:"person" form:"person"` + // 群人数 + MemberNum int32 `json:"memberNum" form:"memberNum"` + // 群人数上限 + Maximum int32 `json:"maximum" form:"maximum"` + // 群状态,0=正常 1=封禁 2=解散 + Status int32 `json:"status" form:"status"` + // 群创建时间 + CreateTime int64 `json:"createTime" form:"createTime"` + // 加群方式,0=无需审批(默认),1=禁止加群,群主和管理员邀请加群, 2=普通人邀请需要审批,群主和管理员直接加群 + JoinType int32 `json:"joinType" form:"joinType"` + // 禁言, 0=全员可发言, 1=全员禁言(除群主和管理员) + MuteType int32 `json:"muteType" form:"muteType"` + // 加好友限制, 0=群内可加好友,1=群内禁止加好友 + FriendType int32 `json:"friendType"` + // 群内当前被禁言的人数 + MuteNum int32 `json:"muteNum"` + // 群内管理员数量 + AdminNum int32 `json:"adminNum"` + // + AESKey string `json:"key"` + // 群类型 (0: 普通群, 1: 全员群, 2: 部门群) + GroupType int32 `json:"groupType"` +} + +type CreateGroupRequest struct { + Name string `json:"name" form:"name"` + Avatar string `json:"avatar" form:"avatar"` + Introduce string `json:"introduce" form:"introduce"` + MemberIds []string `json:"memberIds" form:"memberIds"` + Owner GroupMember `json:"-"` + Members []GroupMember `json:"-"` +} + +type CreateGroupResponse struct { + *GroupInfo + // 群成员 + Members []GroupMember `json:"members" form:"members"` +} + +type InviteGroupMembersRequest struct { + Id int64 `json:"id" form:"id"` + // 如果同时填了 idStr, 则优先选择 idStr + IdStr string `json:"idStr"` + Inviter GroupMember `json:"-"` + NewMembers []GroupMember `json:"-"` + NewMemberIds []string `json:"newMemberIds" form:"newMemberIds" binding:"required"` +} + +type InviteGroupMembersResponse struct { + Id int64 `json:"id" form:"id" example:"123821199217135616"` + IdStr string `json:"idStr"` + MemberNum int32 `json:"memberNum" form:"memberNum" example:"5"` + //Inviter GroupMember `json:"inviter" form:"inviter"` + //NewMembers []GroupMember `json:"newMembers" form:"newMembers"` +} + +type GetGroupInfoRequest struct { + Id int64 `json:"id" uri:"id"` + // 如果同时填了 idStr, 则优先选择 idStr + IdStr string `json:"idStr"` + PersonId string `json:"-"` + DisPlayNum int64 `json:"-"` +} + +type GetGroupInfoResponse struct { + *GroupInfo + Members []*GroupMember `json:"members" form:"members"` +} + +type GetGroupListRequest struct { + PersonId string `json:"-"` +} + +type GetGroupListResponse struct { + Groups []*GroupInfo `json:"groups"` +} + +type GetGroupMemberListRequest struct { + Id int64 `json:"id" uri:"id"` + // 如果同时填了 idStr, 则优先选择 idStr + IdStr string `json:"idStr"` + PersonId string `json:"-"` +} + +type GetGroupMemberListResponse struct { + Id int64 `json:"id"` + // 如果同时填了 idStr, 则优先选择 idStr + IdStr string `json:"idStr"` + Members []*GroupMember `json:"members"` +} + +type GetGroupMemberInfoRequest struct { + Id int64 `json:"id" uri:"id"` + // 如果同时填了 idStr, 则优先选择 idStr + IdStr string `json:"idStr"` + MemberId string `json:"memberId" uri:"memberId" binding:"required"` + PersonId string `json:"-"` +} + +type GetGroupMemberInfoResponse struct { + *GroupMember +} + +type GroupExitRequest struct { + Id int64 `json:"id"` + // 如果同时填了 idStr, 则优先选择 idStr + IdStr string `json:"idStr"` + PersonId string `json:"-"` +} + +type GroupExitResponse struct { +} + +type GroupRemoveRequest struct { + Id int64 `json:"id"` + // 如果同时填了 idStr, 则优先选择 idStr + IdStr string `json:"idStr"` + MemberIds []string `json:"memberIds" binding:"required"` + PersonId string `json:"-"` +} + +type GroupRemoveResponse struct { + // 群人数 + MemberNum int32 `json:"memberNum" form:"memberNum"` + // 成功被踢的成员列表 + MemberIds []string `json:"memberIds"` +} + +type GroupDisbandRequest struct { + Id int64 `json:"id"` + // 如果同时填了 idStr, 则优先选择 idStr + IdStr string `json:"idStr"` + PersonId string `json:"-"` +} + +type GroupDisbandResponse struct { +} + +type UpdateGroupNameRequest struct { + Id int64 `json:"id"` + // 如果同时填了 idStr, 则优先选择 idStr + IdStr string `json:"idStr"` + PersonId string `json:"-"` + Name string `json:"name"` + PublicName string `json:"publicName"` +} + +type UpdateGroupNameResponse struct { +} + +type UpdateGroupAvatarRequest struct { + Id int64 `json:"id"` + // 如果同时填了 idStr, 则优先选择 idStr + IdStr string `json:"idStr"` + PersonId string `json:"-"` + Avatar string `json:"avatar"` +} + +type UpdateGroupAvatarResponse struct { +} + +type UpdateGroupMemberNameRequest struct { + Id int64 `json:"id"` + // 如果同时填了 idStr, 则优先选择 idStr + IdStr string `json:"idStr"` + PersonId string `json:"-"` + MemberName string `json:"memberName"` +} + +type UpdateGroupMemberNameResponse struct { +} + +type UpdateGroupJoinTypeRequest struct { + // 群 ID + Id int64 `json:"id"` + // 如果同时填了 idStr, 则优先选择 idStr + IdStr string `json:"idStr"` + PersonId string `json:"-"` + // 加群方式,0=无需审批(默认),1=禁止加群,群主和管理员邀请加群, 2=普通人邀请需要审批,群主和管理员直接加群 + JoinType int32 `json:"joinType" binding:"oneof=0 1 2"` +} + +type UpdateGroupJoinTypeResponse struct { +} + +type UpdateGroupFriendTypeRequest struct { + // 群 ID + Id int64 `json:"id"` + // 如果同时填了 idStr, 则优先选择 idStr + IdStr string `json:"idStr"` + PersonId string `json:"-"` + // 加好友限制, 0=群内可加好友,1=群内禁止加好友 + FriendType int32 `json:"friendType" binding:"oneof=0 1"` +} + +type UpdateGroupFriendTypeResponse struct { +} + +type ChangeOwnerRequest struct { + // 群 ID + Id int64 `json:"id"` + // 如果同时填了 idStr, 则优先选择 idStr + IdStr string `json:"idStr"` + // 被转让为群主的群成员 ID + MemberId string `json:"memberId" binding:"required"` + PersonId string `json:"-"` +} + +type ChangeOwnerResponse struct { +} + +// JoinGroupReq 扫二维码加群 +type JoinGroupReq struct { + Id int64 `json:"id"` + IdStr string `json:"idStr"` + InviterId string `json:"inviterId"` + PersonId string `json:"-"` +} + +type JoinGroupResp struct { + Id int64 `json:"id"` + IdStr string `json:"idStr"` +} + +type SetAdminRequest struct { + // 群 ID + Id int64 `json:"id"` + // 如果同时填了 idStr, 则优先选择 idStr + IdStr string `json:"idStr"` + // 被设置的群成员 ID + MemberId string `json:"memberId" binding:"required"` + PersonId string `json:"-"` + // 用户角色 0=群员, 1=管理员 + MemberType int32 `json:"memberType" binding:"oneof=0 1"` +} + +type SetAdminResponse struct { +} + +type UpdateGroupMuteTypeRequest struct { + // 群 ID + Id int64 `json:"id"` + // 如果同时填了 idStr, 则优先选择 idStr + IdStr string `json:"idStr"` + // 禁言, 0=全员可发言, 1=全员禁言(除群主和管理员) + MuteType int32 `json:"muteType" binding:"oneof=0 1"` + PersonId string `json:"-"` +} + +type UpdateGroupMuteTypeResponse struct { +} + +type UpdateGroupMemberMuteTimeRequest struct { + // 群 ID + Id int64 `json:"id"` + // 如果同时填了 idStr, 则优先选择 idStr + IdStr string `json:"idStr"` + // 被禁言的群员 ID + MemberIds []string `json:"memberIds" binding:"required"` + // 禁言持续时间, 传9223372036854775807=永久禁言, 0=解除禁言 + MuteTime int64 `json:"muteTime"` + PersonId string `json:"-"` +} + +type UpdateGroupMemberMuteTimeResponse struct { + Members []*GroupMember `json:"members"` +} + +type GetMuteListRequest struct { + // 群 ID + Id int64 `json:"id"` + // 如果同时填了 idStr, 则优先选择 idStr + IdStr string `json:"idStr"` + PersonId string `json:"-"` +} + +type GetMuteListResponse struct { + Members []*GroupMember `json:"members"` +} + +type GetGroupPubInfoRequest struct { + // 群 ID + Id int64 `json:"id"` + // 如果同时填了 idStr, 则优先选择 idStr + IdStr string `json:"idStr"` + PersonId string `json:"-"` +} + +type GetGroupPubInfoResponse struct { + *GroupInfo +} + +type GetGroupInfoByConditionReq struct { + // 查询方法 0:groupMarkId, 1:groupId + Tp int32 `json:"tp" binding:"oneof=0 1"` + Query string `json:"query" binding:"required"` + PersonId string `json:"-"` +} + +type GetGroupInfoByConditionResp struct { + Groups []*GroupInfo `json:"groups"` +} + +// ---群审批------------------ + +// GroupApplyInfo 群审批信息 +type GroupApplyInfo struct { + // 审批 ID + ApplyId string `json:"applyId,omitempty"` + // 群 ID + GroupId string `json:"id,omitempty"` + // 邀请人 ID, 空表示是自己主动申请的 + InviterId string `json:"inviterId,omitempty"` + // 申请加入人 ID + MemberId string `json:"memberId,omitempty"` + // 申请备注 + ApplyNote string `json:"applyNote,omitempty"` + // 审批人 ID + OperatorId string `json:"operatorId,omitempty"` + // 审批情况 0=待审批, 1=审批通过, 2=审批不通过, 10=审批忽略 + ApplyStatus int32 `json:"applyStatus,omitempty"` + // 拒绝原因 + RejectReason string `json:"rejectReason,omitempty"` + // 创建时间 ms + CreateTime int64 `json:"createTime,omitempty"` + // 修改时间 ms + UpdateTime int64 `json:"updateTime,omitempty"` +} + +// CreateGroupApplyReq 创建群审批请求 +type CreateGroupApplyReq struct { + // 群 ID + Id string `json:"id,omitempty" binding:"required"` + // 申请备注 + ApplyNote string `json:"applyNote,omitempty"` + + PersonId string `json:"-"` +} + +// CreateGroupApplyResp 创建群审批响应 +type CreateGroupApplyResp struct { +} + +// GetGroupApplyByIdReq 查询群审批请求 +type GetGroupApplyByIdReq struct { + // 审批 ID + ApplyId string `json:"applyId" binding:"required"` + // 群 ID + Id string `json:"id" binding:"required"` + + PersonId string `json:"-"` +} + +// GetGroupApplysReq 查询群审批列表请求 +type GetGroupApplysReq struct { + // 群 ID + Id string `json:"id" binding:"required"` + // 每页记录数 + Count int32 `json:"count" binding:"required"` + // 当前审批记录数量 + Offset int32 `json:"offset"` + + PersonId string `json:"-"` +} + +// GetGroupApplysResp 查询群审批响应 +type GetGroupApplysResp struct { + GroupApplys []*GroupApplyInfo `json:"applys"` +} + +// AcceptGroupApplyReq 接受加群审批请求 +type AcceptGroupApplyReq struct { + // 审批 ID + ApplyId string `json:"applyId" binding:"required"` + // 群 ID + Id string `json:"id" binding:"required"` + + PersonId string `json:"-"` +} + +type AcceptGroupApplyResp struct { +} + +// RejectGroupApplyReq 拒绝加群审批请求 +type RejectGroupApplyReq struct { + // 审批 ID + ApplyId string `json:"applyId" binding:"required"` + // 群 ID + Id string `json:"id" binding:"required"` + // 拒绝原因 + RejectReason string `json:"rejectReason"` + + PersonId string `json:"-"` +} + +type RejectGroupApplyResp struct { +} diff --git a/service/group/server/grpc/server.go b/service/group/server/grpc/server.go new file mode 100644 index 0000000..89c4fc0 --- /dev/null +++ b/service/group/server/grpc/server.go @@ -0,0 +1,352 @@ +package grpc + +import ( + "context" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/service/group/logic" + "time" + + "gitlab.33.cn/chat/dtalk/pkg/interceptor/logger" + "gitlab.33.cn/chat/dtalk/pkg/interceptor/trace" + xgrpc "gitlab.33.cn/chat/dtalk/pkg/net/grpc" + pb "gitlab.33.cn/chat/dtalk/service/group/api" + "gitlab.33.cn/chat/dtalk/service/group/model" + "gitlab.33.cn/chat/dtalk/service/group/model/biz" + "gitlab.33.cn/chat/dtalk/service/group/model/types" + "gitlab.33.cn/chat/dtalk/service/group/service" + "google.golang.org/grpc" +) + +func New(cfg *xgrpc.ServerConfig, svr *service.Service) *xgrpc.Server { + connectionTimeout := grpc.ConnectionTimeout(time.Duration(cfg.Timeout)) + logServerInterceptor := logger.NewServerInterceptor(svr.GetLog(), []string{ + "/dtalk.group.Group/GetGroupIds", + "/dtalk.group.Group/CheckInGroup", + "/dtalk.group.Group/GetMemberIds", + "/dtalk.group.Group/CheckMute", + "/dtalk.group.Group/GetGroups", + "/dtalk.group.Group/GetMember", + "/dtalk.group.Group/GetGroupInfo", + }) + + var ws = xgrpc.NewServer( + cfg, + connectionTimeout, + grpc.ChainUnaryInterceptor( + xerror.ErrInterceptor, + trace.ServerUnaryInterceptor, + logServerInterceptor.Unary, + ), + ) + pb.RegisterGroupServer(ws.Server(), &server{svc: svr}) + ws, err := ws.Start() + if err != nil { + panic(err) + } + return ws +} + +type server struct { + pb.UnimplementedGroupServer + svc *service.Service +} + +// GetGroupIds . +func (s *server) GetGroupIds(ctx context.Context, req *pb.GetGroupIdsRequest) (*pb.GetGroupIdsReply, error) { + memberId := req.MemberId + groupIds, err := s.svc.GetGroupIdsByMemberId(memberId) + if err != nil { + return nil, err + } + + return &pb.GetGroupIdsReply{GroupIds: groupIds}, nil +} + +// CheckInGroup . +func (s *server) CheckInGroup(ctx context.Context, req *pb.CheckInGroupRequest) (*pb.CheckInGroupReply, error) { + memberId := req.MemberId + groupId := req.GroupId + isOk, err := s.svc.CheckInGroup(memberId, groupId) + if err != nil { + return nil, err + } + return &pb.CheckInGroupReply{IsOk: isOk}, nil +} + +// GetMemberIds 单独开一个得到 id 的 +func (s *server) GetMemberIds(ctx context.Context, req *pb.GetMemberIdsRequest) (*pb.GetMemberIdsReply, error) { + groupId := req.GroupId + groupMembers, err := s.svc.GetMembersByGroupId(groupId) + if err != nil { + return nil, err + } + groupMemberIds := make([]string, len(groupMembers), len(groupMembers)) + for i := range groupMembers { + groupMemberIds[i] = groupMembers[i].GroupMemberId + } + return &pb.GetMemberIdsReply{MemberIds: groupMemberIds}, nil +} + +// CheckMute . +func (s *server) CheckMute(ctx context.Context, req *pb.CheckMuteRequest) (*pb.CheckMuteReply, error) { + groupId := req.GroupId + memberId := req.MemberId + muteTime, err := s.svc.GetGroupMemberMuteTime(groupId, memberId) + if err != nil { + return nil, err + } + nowTime := time.Now().UnixNano() / 1e6 + res := &pb.CheckMuteReply{IsOk: false} + if muteTime > nowTime { + res.IsOk = true + } + return res, nil +} + +// GetGroups 获得群列表 +func (s *server) GetGroups(ctx context.Context, req *pb.GetGroupsReq) (*pb.GetGroupsResp, error) { + getGroupsReq := &types.GetGroupListRequest{ + PersonId: req.Id, + } + + getGroupsResp, err := s.svc.GetGroupListSvc(ctx, getGroupsReq) + if err != nil { + return nil, err + } + + groupsInfo := make([]*pb.GroupInfo, len(getGroupsResp.Groups)) + for i, group := range getGroupsResp.Groups { + groupsInfo[i] = &pb.GroupInfo{ + Id: group.Id, + Name: group.Name, + Avatar: group.Avatar, + } + } + + return &pb.GetGroupsResp{ + Groups: groupsInfo, + }, nil + +} + +// GetMember . +func (s *server) GetMember(ctx context.Context, req *pb.GetMemberReq) (*pb.GetMemberResp, error) { + groupId := req.GroupId + memberId := req.MemberId + + mem, err := s.svc.GetMemberByMemberIdAndGroupId(ctx, memberId, groupId) + if err != nil { + if err == model.ErrRecordNotExist { + return &pb.GetMemberResp{ + GroupMemberType: biz.GroupMemberTypeOther, + }, nil + } + return nil, err + } + + return &pb.GetMemberResp{ + GroupId: mem.GroupId, + GroupMemberId: mem.GroupMemberId, + GroupMemberName: mem.GroupMemberName, + GroupMemberType: mem.GroupMemberType, + GroupMemberJoinTime: mem.GroupMemberJoinTime, + GroupMemberUpdateTime: 0, + }, nil +} + +// ----------------- new ---------------- + +// GetGroupInfo 根据一个群 id 查询一个群信息 +// oa 使用, 返回群信息和判断群是否被解散 +func (s *server) GetGroupInfo(ctx context.Context, req *pb.GetGroupInfoReq) (*pb.GetGroupInfoResp, error) { + groupId := req.GroupId + + group, err := s.svc.GetGroupInfoByGroupId(ctx, groupId) + if err != nil { + if err.Error() == xerror.NewError(xerror.GroupStatusDisBand).Error() { + return &pb.GetGroupInfoResp{ + GroupId: groupId, + GroupExist: false, + GroupName: "", + GroupAvatar: "", + GroupOwnerId: "", + }, nil + } else { + return nil, err + } + } + + return &pb.GetGroupInfoResp{ + GroupId: group.GroupId, + GroupExist: true, + GroupName: group.GroupPubName, + GroupAvatar: group.GroupAvatar, + GroupOwnerId: group.GroupOwnerId, + }, nil +} + +// ForceAddMember 增加群成员 +// oa 专属 +func (s *server) ForceAddMember(ctx context.Context, req *pb.ForceAddMemberReq) (*pb.ForceAddMemberResp, error) { + l := logic.NewForceAddMemberLogic(ctx, s.svc) + return l.ForceAddMember(req) +} + +// ForceDeleteMember 删除群成员 +// oa 专属, 退部门的时候退群 +func (s *server) ForceDeleteMember(ctx context.Context, req *pb.ForceDeleteMemberReq) (*pb.ForceDeleteMemberResp, error) { + l := logic.NewForceDeleteMemberLogic(ctx, s.svc) + return l.ForceDeleteMember(req) +} + +// ForceDisbandGroup . +func (s *server) ForceDisbandGroup(ctx context.Context, req *pb.ForceDisbandGroupReq) (*pb.ForceDisbandGroupResp, error) { + err := s.svc.GroupDisband(ctx, req.GroupId, req.OpeId) + if err != nil { + return nil, err + } + + return &pb.ForceDisbandGroupResp{}, nil +} + +// ForceUpdateGroupType . +func (s *server) ForceUpdateGroupType(ctx context.Context, req *pb.ForceUpdateGroupTypeReq) (*pb.ForceUpdateGroupTypeResp, error) { + err := s.svc.UpdateGroupType(req.GroupId, req.GroupType) + if err != nil { + return nil, err + } + + return &pb.ForceUpdateGroupTypeResp{}, nil +} + +// ForceAddMembers . +func (s *server) ForceAddMembers(ctx context.Context, req *pb.ForceAddMembersReq) (*pb.ForceAddMembersResp, error) { + l := logic.NewForceAddMembersLogic(ctx, s.svc) + return l.ForceAddMembers(req) +} + +// ForceDeleteMembers . +func (s *server) ForceDeleteMembers(ctx context.Context, req *pb.ForceDeleteMembersReq) (*pb.ForceDeleteMembersResp, error) { + l := logic.NewForceDeleteMembersLogic(ctx, s.svc) + return l.ForceDeleteMembers(req) +} + +// ForceJoinGroups . +func (s *server) ForceJoinGroups(ctx context.Context, req *pb.ForceJoinGroupsReq) (*pb.ForceJoinGroupsResp, error) { + l := logic.NewForceJoinGroupsLogic(ctx, s.svc) + return l.ForceJoinGroups(req) +} + +// ForceExitGroups . +func (s *server) ForceExitGroups(ctx context.Context, req *pb.ForceExitGroupsReq) (*pb.ForceExitGroupsResp, error) { + l := logic.NewForceExitGroupsLogic(ctx, s.svc) + return l.ForceExitGroups(req) +} + +// ForceChangeOwner . +func (s *server) ForceChangeOwner(ctx context.Context, req *pb.ForceChangeOwnerReq) (*pb.ForceChangeOwnerResp, error) { + l := logic.NewForceChangeOwnerLogic(ctx, s.svc) + return l.ForceChangeOwner(req) +} + +// CreateGroup . +func (s *server) CreateGroup(ctx context.Context, req *pb.CreateGroupReq) (*pb.CreateGroupResp, error) { + l := logic.NewCreateGroupLogic(ctx, s.svc) + return l.CreateGroup(req) +} + +// ChangeOwner . +func (s *server) ChangeOwner(ctx context.Context, req *pb.ChangeOwnerReq) (*pb.ChangeOwnerResp, error) { + l := logic.NewChangeOwnerLogic(ctx, s.svc) + return l.ChangeOwner(req) +} + +func (s *server) GetGroupList(ctx context.Context, req *pb.GetGroupListReq) (*pb.GetGroupListResp, error) { + l := logic.NewGetGroupListLogic(ctx, s.svc) + return l.GetGroupList(req) +} + +func (s *server) GetGroupMemberInfo(ctx context.Context, req *pb.GetGroupMemberInfoReq) (*pb.GetGroupMemberInfoResp, error) { + l := logic.NewGetGroupMemberInfoLogic(ctx, s.svc) + return l.GetGroupMemberInfo(req) +} + +func (s *server) GetGroupMemberList(ctx context.Context, req *pb.GetGroupMemberListReq) (*pb.GetGroupMemberListResp, error) { + l := logic.NewGetGroupMemberListLogic(ctx, s.svc) + return l.GetGroupMemberList(req) +} + +func (s *server) GetMuteList(ctx context.Context, req *pb.GetMuteListReq) (*pb.GetMuteListResp, error) { + l := logic.NewGetMuteListLogic(ctx, s.svc) + return l.GetMuteList(req) +} + +func (s *server) GetPriGroupInfo(ctx context.Context, req *pb.GetPriGroupInfoReq) (*pb.GetPriGroupInfoResp, error) { + l := logic.NewGetPriGroupInfoLogic(ctx, s.svc) + return l.GetPriGroupInfo(req) +} + +func (s *server) GetPubGroupInfo(ctx context.Context, req *pb.GetPubGroupInfoReq) (*pb.GetPubGroupInfoResp, error) { + l := logic.NewGetPubGroupInfoLogic(ctx, s.svc) + return l.GetPubGroupInfo(req) +} + +func (s *server) GroupDisband(ctx context.Context, req *pb.GroupDisbandReq) (*pb.GroupDisbandResp, error) { + l := logic.NewGroupDisbandLogic(ctx, s.svc) + return l.GroupDisband(req) +} + +func (s *server) GroupExit(ctx context.Context, req *pb.GroupExitReq) (*pb.GroupExitResp, error) { + l := logic.NewGroupExitLogic(ctx, s.svc) + return l.GroupExit(req) +} + +func (s *server) GroupRemove(ctx context.Context, req *pb.GroupRemoveReq) (*pb.GroupRemoveResp, error) { + l := logic.NewGroupRemoveLogic(ctx, s.svc) + return l.GroupRemove(req) +} + +func (s *server) InviteGroupMembers(ctx context.Context, req *pb.InviteGroupMembersReq) (*pb.InviteGroupMembersResp, error) { + l := logic.NewInviteGroupMembersLogic(ctx, s.svc) + return l.InviteGroupMembers(req) +} + +func (s *server) SetAdmin(ctx context.Context, req *pb.SetAdminReq) (*pb.SetAdminResp, error) { + l := logic.NewSetAdminLogic(ctx, s.svc) + return l.SetAdmin(req) +} + +func (s *server) UpdateGroupAvatar(ctx context.Context, req *pb.UpdateGroupAvatarReq) (*pb.UpdateGroupAvatarResp, error) { + l := logic.NewUpdateGroupAvatarLogic(ctx, s.svc) + return l.UpdateGroupAvatar(req) +} + +func (s *server) UpdateGroupFriendType(ctx context.Context, req *pb.UpdateGroupFriendTypeReq) (*pb.UpdateGroupFriendTypeResp, error) { + l := logic.NewUpdateGroupFriendTypeLogic(ctx, s.svc) + return l.UpdateGroupFriendType(req) +} + +func (s *server) UpdateGroupJoinType(ctx context.Context, req *pb.UpdateGroupJoinTypeReq) (*pb.UpdateGroupJoinTypeResp, error) { + l := logic.NewUpdateGroupJoinTypeLogic(ctx, s.svc) + return l.UpdateGroupJoinType(req) +} + +func (s *server) UpdateGroupMemberMuteTime(ctx context.Context, req *pb.UpdateGroupMemberMuteTimeReq) (*pb.UpdateGroupMemberMuteTimeResp, error) { + l := logic.NewUpdateGroupMemberMuteTimeLogic(ctx, s.svc) + return l.UpdateGroupMemberMuteTime(req) +} + +func (s *server) UpdateGroupMemberName(ctx context.Context, req *pb.UpdateGroupMemberNameReq) (*pb.UpdateGroupMemberNameResp, error) { + l := logic.NewUpdateGroupMemberNameLogic(ctx, s.svc) + return l.UpdateGroupMemberName(req) +} + +func (s *server) UpdateGroupMuteType(ctx context.Context, req *pb.UpdateGroupMuteTypeReq) (*pb.UpdateGroupMuteTypeResp, error) { + l := logic.NewUpdateGroupMuteTypeLogic(ctx, s.svc) + return l.UpdateGroupMuteType(req) +} + +func (s *server) UpdateGroupName(ctx context.Context, req *pb.UpdateGroupNameReq) (*pb.UpdateGroupNameResp, error) { + l := logic.NewUpdateGroupNameLogic(ctx, s.svc) + return l.UpdateGroupName(req) +} diff --git a/service/group/server/http/group.go b/service/group/server/http/group.go new file mode 100644 index 0000000..df84e17 --- /dev/null +++ b/service/group/server/http/group.go @@ -0,0 +1,974 @@ +package http + +import ( + "github.com/gin-gonic/gin" + "gitlab.33.cn/chat/dtalk/pkg/api" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + logic "gitlab.33.cn/chat/dtalk/service/group/logic/http" + "gitlab.33.cn/chat/dtalk/service/group/model/biz" + "gitlab.33.cn/chat/dtalk/service/group/model/types" + "gitlab.33.cn/utils/go-kit/convert" +) + +// CreateGroup +// @Summary 创建群 +// @Author chy@33.cn +// @Tags group 群动作 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.CreateGroupRequest false "body" +// @Success 200 {object} types.GeneralResponse{data=types.CreateGroupResponse} +// @Router /app/create-group [post] +func CreateGroup(c *gin.Context) { + userId, ok := c.Get(api.Address) + if !ok { + c.Set(api.ReqError, xerror.NewError(xerror.SignatureInvalid)) + return + } + req := &types.CreateGroupRequest{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + req.Owner.MemberId = userId.(string) + req.Owner.MemberName = "" + req.Owner.MemberType = biz.GroupMemberTypeOwner + + req.Members = make([]types.GroupMember, len(req.MemberIds), len(req.MemberIds)) + for i, id := range req.MemberIds { + req.Members[i].MemberId = id + req.Members[i].MemberName = "" + req.Members[i].MemberType = biz.GroupMemberTypeNormal + } + + res, err := svc.CreateGroupSvc(c, req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) + +} + +// InviteGroupMembers +// @Summary 邀请新群员 +// @Author chy@33.cn +// @Tags group 群动作 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.InviteGroupMembersRequest false "body" +// @Success 200 {object} types.GeneralResponse{data=types.InviteGroupMembersResponse} +// @Router /app/invite-group-members [post] +func InviteGroupMembers(c *gin.Context) { + userId, ok := c.Get(api.Address) + if !ok { + c.Set(api.ReqError, xerror.NewError(xerror.SignatureInvalid)) + return + } + req := &types.InviteGroupMembersRequest{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + if req.Id == 0 && req.IdStr == "" { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.IdStr != "" { + req.Id = convert.ToInt64(req.IdStr) + } + + req.Inviter.MemberId = userId.(string) + req.NewMembers = make([]types.GroupMember, len(req.NewMemberIds), len(req.NewMemberIds)) + for i, id := range req.NewMemberIds { + req.NewMembers[i].MemberId = id + req.NewMembers[i].MemberName = "" + req.NewMembers[i].MemberType = biz.GroupMemberTypeNormal + } + + res, err := svc.InviteGroupMembersSvc(c, req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) +} + +// JoinGroup +// @Summary 直接进群 +// @Author chy@33.cn +// @Tags group 群动作 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.JoinGroupReq false "body" +// @Success 200 {object} types.GeneralResponse{data=types.JoinGroupResp} +// @Router /app/join-group [post] +func JoinGroup(c *gin.Context) { + userId, ok := c.Get(api.Address) + if !ok { + c.Set(api.ReqError, xerror.NewError(xerror.SignatureInvalid)) + return + } + req := &types.JoinGroupReq{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + if req.Id == 0 && req.IdStr == "" { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.IdStr != "" { + req.Id = convert.ToInt64(req.IdStr) + } + + req.PersonId = userId.(string) + + // 走邀请函数 + nreq := &types.InviteGroupMembersRequest{} + nreq.Id = req.Id + nreq.Inviter.MemberId = req.InviterId + nreq.NewMembers = make([]types.GroupMember, 1, 1) + nreq.NewMembers[0].MemberId = req.PersonId + nreq.NewMembers[0].MemberName = "" + nreq.NewMembers[0].MemberType = biz.GroupMemberTypeNormal + + res, err := svc.InviteGroupMembersSvc(c, nreq) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) +} + +// GroupExit +// @Summary 退群 +// @Author chy@33.cn +// @Tags group 群动作 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.GroupExitRequest false "body" +// @Success 200 {object} types.GeneralResponse{data=types.GroupExitResponse} +// @Router /app/group-exit [post] +func GroupExit(c *gin.Context) { + userId, ok := c.Get(api.Address) + if !ok { + c.Set(api.ReqError, xerror.NewError(xerror.SignatureInvalid)) + return + } + req := &types.GroupExitRequest{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.Id == 0 && req.IdStr == "" { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.IdStr != "" { + req.Id = convert.ToInt64(req.IdStr) + } + + req.PersonId = userId.(string) + + res, err := svc.GroupExitHttp(c, req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) +} + +// GroupRemove +// @Summary 踢人 +// @Author chy@33.cn +// @Tags group 群动作 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.GroupRemoveRequest false "body" +// @Success 200 {object} types.GeneralResponse{data=types.GroupRemoveResponse} +// @Router /app/group-remove [post] +func GroupRemove(c *gin.Context) { + userId, ok := c.Get(api.Address) + if !ok { + c.Set(api.ReqError, xerror.NewError(xerror.SignatureInvalid)) + return + } + req := &types.GroupRemoveRequest{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.Id == 0 && req.IdStr == "" { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.IdStr != "" { + req.Id = convert.ToInt64(req.IdStr) + } + + req.PersonId = userId.(string) + + res, err := svc.GroupRemoveSvc(c, req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) +} + +// GroupDisband +// @Summary 解散群 +// @Author chy@33.cn +// @Tags group 群动作 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.GroupDisbandRequest false "body" +// @Success 200 {object} types.GeneralResponse{data=types.GroupDisbandResponse} +// @Router /app/group-disband [post] +func GroupDisband(c *gin.Context) { + req := &types.GroupDisbandRequest{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.Id == 0 && req.IdStr == "" { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.IdStr != "" { + req.Id = convert.ToInt64(req.IdStr) + } + + userId, ok := c.Get(api.Address) + if !ok { + c.Set(api.ReqError, xerror.NewError(xerror.SignatureInvalid)) + return + } + + req.PersonId = userId.(string) + + res, err := svc.GroupDisbandHttp(c, req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) +} + +// ChangeOwner +// @Summary 转让群 +// @Author chy@33.cn +// @Tags group 群动作 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.ChangeOwnerRequest false "body" +// @Success 200 {object} types.GeneralResponse{data=types.ChangeOwnerResponse} +// @Router /app/change-owner [post] +func ChangeOwner(c *gin.Context) { + req := &types.ChangeOwnerRequest{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + if req.Id == 0 && req.IdStr == "" { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.IdStr != "" { + req.Id = convert.ToInt64(req.IdStr) + } + + userId, ok := c.Get(api.Address) + if !ok { + c.Set(api.ReqError, xerror.NewError(xerror.SignatureInvalid)) + return + } + + req.PersonId = userId.(string) + + l := logic.NewChangeOwnerLogic(c, svc) + res, err := l.ChangeOwner(req) + + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) +} + +// GetGroupInfo +// @Summary 查询群信息 +// @Author chy@33.cn +// @Tags group 群信息 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.GetGroupInfoRequest false "body" +// @Success 200 {object} types.GeneralResponse{data=types.GetGroupInfoResponse} +// @Router /app/group-info [post] +func GetGroupInfo(c *gin.Context) { + userId, ok := c.Get(api.Address) + if !ok { + c.Set(api.ReqError, xerror.NewError(xerror.SignatureInvalid)) + return + } + req := &types.GetGroupInfoRequest{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + if req.Id == 0 && req.IdStr == "" { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.IdStr != "" { + req.Id = convert.ToInt64(req.IdStr) + } + + req.PersonId = userId.(string) + if req.DisPlayNum == 0 { + req.DisPlayNum = biz.DisPlayNum + } + + res, err := svc.GetGroupInfoHttp(c, req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) + +} + +// GetGroupPubInfo +// @Summary 查询群公开信息 +// @Author chy@33.cn +// @Tags group 群信息 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.GetGroupPubInfoRequest false "body" +// @Success 200 {object} types.GeneralResponse{data=types.GetGroupPubInfoResponse} +// @Router /app/group-pub-info [post] +func GetGroupPubInfo(c *gin.Context) { + req := &types.GetGroupPubInfoRequest{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + if req.Id == 0 && req.IdStr == "" { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.IdStr != "" { + req.Id = convert.ToInt64(req.IdStr) + } + + userId, ok := c.Get(api.Address) + if !ok { + c.Set(api.ReqError, xerror.NewError(xerror.SignatureInvalid)) + return + } + req.PersonId = userId.(string) + + res, err := svc.GetGroupPubInfoSvc(c, req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) + +} + +// GetGroupList +// @Summary 查询群列表 +// @Author chy@33.cn +// @Tags group 群信息 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.GetGroupListRequest false "body" +// @Success 200 {object} types.GeneralResponse{data=types.GetGroupListResponse} +// @Router /app/group-list [post] +func GetGroupList(c *gin.Context) { + userId, ok := c.Get(api.Address) + if !ok { + c.Set(api.ReqError, xerror.NewError(xerror.SignatureInvalid)) + return + } + req := &types.GetGroupListRequest{} + req.PersonId = userId.(string) + + res, err := svc.GetGroupListSvc(c, req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) + +} + +// GetGroupMemberList +// @Summary 查询群成员列表 +// @Author chy@33.cn +// @Tags group 群成员信息 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.GetGroupMemberListRequest false "body" +// @Success 200 {object} types.GeneralResponse{data=types.GetGroupMemberListResponse} +// @Router /app/group-member-list [post] +func GetGroupMemberList(c *gin.Context) { + req := &types.GetGroupMemberListRequest{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + if req.Id == 0 && req.IdStr == "" { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.IdStr != "" { + req.Id = convert.ToInt64(req.IdStr) + } + + userId, ok := c.Get(api.Address) + if !ok { + c.Set(api.ReqError, xerror.NewError(xerror.SignatureInvalid)) + return + } + + req.PersonId = userId.(string) + + res, err := svc.GetGroupMemberListSvc(c, req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) +} + +// GetGroupMemberInfo +// @Summary 查询群成员信息 +// @Author chy@33.cn +// @Tags group 群成员信息 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.GetGroupMemberInfoRequest false "body" +// @Success 200 {object} types.GeneralResponse{data=types.GetGroupMemberInfoResponse} +// @Router /app/group-member-info [post] +func GetGroupMemberInfo(c *gin.Context) { + req := &types.GetGroupMemberInfoRequest{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + if req.Id == 0 && req.IdStr == "" { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.IdStr != "" { + req.Id = convert.ToInt64(req.IdStr) + } + + userId, ok := c.Get(api.Address) + if !ok { + c.Set(api.ReqError, xerror.NewError(xerror.SignatureInvalid)) + return + } + req.PersonId = userId.(string) + + res, err := svc.GetGroupMemberInfoSvc(c, req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) +} + +// UpdateGroupName +// @Summary 更新群名称 +// @Author chy@33.cn +// @Tags group 群信息 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.UpdateGroupNameRequest false "body" +// @Success 200 {object} types.GeneralResponse{data=types.UpdateGroupNameResponse} +// @Router /app/name [post] +func UpdateGroupName(c *gin.Context) { + req := &types.UpdateGroupNameRequest{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + if req.Id == 0 && req.IdStr == "" { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.IdStr != "" { + req.Id = convert.ToInt64(req.IdStr) + } + + userId, ok := c.Get(api.Address) + if !ok { + c.Set(api.ReqError, xerror.NewError(xerror.SignatureInvalid)) + return + } + + req.PersonId = userId.(string) + + res, err := svc.UpdateGroupNameSvc(c, req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) +} + +// UpdateGroupAvatar +// @Summary 更新群头像 +// @Author chy@33.cn +// @Tags group 群信息 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.UpdateGroupAvatarRequest false "body" +// @Success 200 {object} types.GeneralResponse{data=types.UpdateGroupAvatarResponse} +// @Router /app/avatar [post] +func UpdateGroupAvatar(c *gin.Context) { + req := &types.UpdateGroupAvatarRequest{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + if req.Id == 0 && req.IdStr == "" { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.IdStr != "" { + req.Id = convert.ToInt64(req.IdStr) + } + + userId, ok := c.Get(api.Address) + if !ok { + c.Set(api.ReqError, xerror.NewError(xerror.SignatureInvalid)) + return + } + + req.PersonId = userId.(string) + + res, err := svc.UpdateGroupAvatarSvc(c, req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) +} + +// UpdateGroupJoinType +// @Summary 更新加群设置 +// @Author chy@33.cn +// @Tags group 群信息 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.UpdateGroupJoinTypeRequest false "body" +// @Success 200 {object} types.GeneralResponse{data=types.UpdateGroupJoinTypeResponse} +// @Router /app/joinType [post] +func UpdateGroupJoinType(c *gin.Context) { + req := &types.UpdateGroupJoinTypeRequest{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + if req.Id == 0 && req.IdStr == "" { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.IdStr != "" { + req.Id = convert.ToInt64(req.IdStr) + } + + userId, ok := c.Get(api.Address) + if !ok { + c.Set(api.ReqError, xerror.NewError(xerror.SignatureInvalid)) + return + } + + req.PersonId = userId.(string) + + res, err := svc.UpdateGroupJoinTypeSvc(c, req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) +} + +// UpdateGroupFriendType +// @Summary 更新群内加好友设置 +// @Author chy@33.cn +// @Tags group 群信息 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.UpdateGroupFriendTypeRequest false "body" +// @Success 200 {object} types.GeneralResponse{data=types.UpdateGroupFriendTypeResponse} +// @Router /app/friendType [post] +func UpdateGroupFriendType(c *gin.Context) { + req := &types.UpdateGroupFriendTypeRequest{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + if req.Id == 0 && req.IdStr == "" { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.IdStr != "" { + req.Id = convert.ToInt64(req.IdStr) + } + + userId, ok := c.Get(api.Address) + if !ok { + c.Set(api.ReqError, xerror.NewError(xerror.SignatureInvalid)) + return + } + + req.PersonId = userId.(string) + + res, err := svc.UpdateGroupFriendTypeSvc(c, req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) +} + +// UpdateGroupMemberName +// @Summary 更新群成员昵称 +// @Author chy@33.cn +// @Tags group 群成员信息 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.UpdateGroupMemberNameRequest false "body" +// @Success 200 {object} types.GeneralResponse{data=types.UpdateGroupMemberNameResponse} +// @Router /app/member/name [post] +func UpdateGroupMemberName(c *gin.Context) { + req := &types.UpdateGroupMemberNameRequest{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + if req.Id == 0 && req.IdStr == "" { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.IdStr != "" { + req.Id = convert.ToInt64(req.IdStr) + } + + userId, ok := c.Get(api.Address) + if !ok { + c.Set(api.ReqError, xerror.NewError(xerror.SignatureInvalid)) + return + } + + req.PersonId = userId.(string) + + res, err := svc.UpdateMemberNameSvc(c, req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) +} + +// SetAdmin +// @Summary 设置管理员 +// @Author chy@33.cn +// @Tags group 群成员信息 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.SetAdminRequest false "body" +// @Success 200 {object} types.GeneralResponse{data=types.SetAdminResponse} +// @Router /app/member/type [post] +func SetAdmin(c *gin.Context) { + req := &types.SetAdminRequest{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + if req.Id == 0 && req.IdStr == "" { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.IdStr != "" { + req.Id = convert.ToInt64(req.IdStr) + } + + userId, ok := c.Get(api.Address) + if !ok { + c.Set(api.ReqError, xerror.NewError(xerror.SignatureInvalid)) + return + } + + req.PersonId = userId.(string) + + res, err := svc.SetAdminSvc(c, req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) +} + +// UpdateGroupMuteType +// @Summary 更新群禁言设置 +// @Author chy@33.cn +// @Tags group 群信息 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.UpdateGroupMuteTypeRequest false "body" +// @Success 200 {object} types.GeneralResponse{data=types.UpdateGroupMuteTypeResponse} +// @Router /app/muteType [post] +func UpdateGroupMuteType(c *gin.Context) { + req := &types.UpdateGroupMuteTypeRequest{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + if req.Id == 0 && req.IdStr == "" { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.IdStr != "" { + req.Id = convert.ToInt64(req.IdStr) + } + + userId, ok := c.Get(api.Address) + if !ok { + c.Set(api.ReqError, xerror.NewError(xerror.SignatureInvalid)) + return + } + + req.PersonId = userId.(string) + + res, err := svc.UpdateGroupMuteTypeSvc(c, req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) +} + +// SetMembersMuteTime +// @Summary 更新群成员禁言时间 +// @Author chy@33.cn +// @Tags group 禁言 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.UpdateGroupMemberMuteTimeRequest false "body" +// @Success 200 {object} types.GeneralResponse{data=types.UpdateGroupMemberMuteTimeResponse} +// @Router /app/member/muteTime [post] +func SetMembersMuteTime(c *gin.Context) { + req := &types.UpdateGroupMemberMuteTimeRequest{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + if req.Id == 0 && req.IdStr == "" { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.IdStr != "" { + req.Id = convert.ToInt64(req.IdStr) + } + + userId, ok := c.Get(api.Address) + if !ok { + c.Set(api.ReqError, xerror.NewError(xerror.SignatureInvalid)) + return + } + + req.PersonId = userId.(string) + + res, err := svc.UpdateMembersMuteTimeSvc(c, req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) +} + +// GetMuteList +// @Summary 查询群内被禁言成员名单 +// @Author chy@33.cn +// @Tags group 禁言 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.GetMuteListRequest false "body" +// @Success 200 {object} types.GeneralResponse{data=types.GetMuteListResponse} +// @Router /app/mute-list [post] +func GetMuteList(c *gin.Context) { + req := &types.GetMuteListRequest{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + if req.Id == 0 && req.IdStr == "" { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.IdStr != "" { + req.Id = convert.ToInt64(req.IdStr) + } + + userId, ok := c.Get(api.Address) + if !ok { + c.Set(api.ReqError, xerror.NewError(xerror.SignatureInvalid)) + return + } + + req.PersonId = userId.(string) + + res, err := svc.GetMuteListSvc(c, req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) +} + +// GetGroupInfoById +// @Summary 查询群信息 +// @Author chy@33.cn +// @Tags group 群信息 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param id path integer true "群id" +// @Success 200 {object} types.GeneralResponse{data=types.GetGroupInfoResponse} +// @Router /app/group/{id} [get] +func GetGroupInfoById(c *gin.Context) { + userId, ok := c.Get(api.Address) + if !ok { + c.Set(api.ReqError, xerror.NewError(xerror.SignatureInvalid)) + return + } + req := &types.GetGroupInfoRequest{} + err := c.ShouldBindUri(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + if req.Id == 0 && req.IdStr == "" { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.IdStr != "" { + req.Id = convert.ToInt64(req.IdStr) + } + + req.PersonId = userId.(string) + if req.DisPlayNum == 0 { + req.DisPlayNum = biz.DisPlayNum + } + + res, err := svc.GetGroupInfoHttp(c, req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) +} + +// GetGroups +// @Summary 查询群列表 +// @Author chy@33.cn +// @Tags group 群信息 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Success 200 {object} types.GeneralResponse{data=types.GetGroupListResponse} +// @Router /app/groups [get] +func GetGroups(c *gin.Context) { + userId, ok := c.Get(api.Address) + if !ok { + c.Set(api.ReqError, xerror.NewError(xerror.SignatureInvalid)) + return + } + req := &types.GetGroupListRequest{} + req.PersonId = userId.(string) + + res, err := svc.GetGroupListSvc(c, req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) + +} + +// GetGroupMemberInfoByUri +// @Summary 查询群成员信息 +// @Author chy@33.cn +// @Tags group 群成员信息 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param id path integer true "群id" +// @Param memberId path integer true "群成员id" +// @Success 200 {object} types.GeneralResponse{data=types.GetGroupMemberInfoResponse} +// @Router /app/group/{id}/member/{memberId} [get] +func GetGroupMemberInfoByUri(c *gin.Context) { + req := &types.GetGroupMemberInfoRequest{} + err := c.ShouldBindUri(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + if req.Id == 0 && req.IdStr == "" { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.IdStr != "" { + req.Id = convert.ToInt64(req.IdStr) + } + + userId, ok := c.Get(api.Address) + if !ok { + c.Set(api.ReqError, xerror.NewError(xerror.SignatureInvalid)) + return + } + req.PersonId = userId.(string) + + res, err := svc.GetGroupMemberInfoSvc(c, req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) +} + +// GetGroupMemberListByUri +// @Summary 查询群成员列表 +// @Author chy@33.cn +// @Tags group 群成员信息 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param id path integer true "群id" +// @Success 200 {object} types.GeneralResponse{data=types.GetGroupMemberListResponse} +// @Router /app/group/{id}/members [get] +func GetGroupMemberListByUri(c *gin.Context) { + req := &types.GetGroupMemberListRequest{} + err := c.ShouldBindUri(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + if req.Id == 0 && req.IdStr == "" { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError)) + return + } + + if req.IdStr != "" { + req.Id = convert.ToInt64(req.IdStr) + } + + userId, ok := c.Get(api.Address) + if !ok { + c.Set(api.ReqError, xerror.NewError(xerror.SignatureInvalid)) + return + } + + req.PersonId = userId.(string) + + res, err := svc.GetGroupMemberListSvc(c, req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) +} + +// GetGroupInfoByCondition +// @Summary 搜索群列表 +// @Author chy@33.cn +// @Tags group 群信息 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.GetGroupInfoByConditionReq false "body" +// @Success 200 {object} types.GeneralResponse{data=types.GetGroupInfoByConditionResp} +// @Router /app/group-search [post] +func GetGroupInfoByCondition(c *gin.Context) { + userId, ok := c.Get(api.Address) + if !ok { + c.Set(api.ReqError, xerror.NewError(xerror.SignatureInvalid)) + return + } + req := &types.GetGroupInfoByConditionReq{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + req.PersonId = userId.(string) + + res, err := svc.GetGroupInfoByConditionSvc(c, req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) +} diff --git a/service/group/server/http/groupapply.go b/service/group/server/http/groupapply.go new file mode 100644 index 0000000..a50f515 --- /dev/null +++ b/service/group/server/http/groupapply.go @@ -0,0 +1,152 @@ +package http + +import ( + "github.com/gin-gonic/gin" + "gitlab.33.cn/chat/dtalk/pkg/api" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/service/group/model/types" +) + +// CreateGroupApply +// @Summary 创建加群审批 +// @Author chy@33.cn +// @Tags group 群审批 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.CreateGroupApplyReq false "body" +// @Success 200 {object} types.GeneralResponse{data=types.CreateGroupApplyResp} +// @Router /app/create-group-apply [post] +func CreateGroupApply(c *gin.Context) { + userId, ok := c.Get(api.Address) + if !ok { + c.Set(api.ReqError, xerror.NewError(xerror.SignatureInvalid)) + return + } + req := &types.CreateGroupApplyReq{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + req.PersonId = userId.(string) + + res, err := svc.CreateGroupApplySvc(c, req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) +} + +// AcceptGroupApply +// @Summary 接受加群审批 +// @Author chy@33.cn +// @Tags group 群审批 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.AcceptGroupApplyReq false "body" +// @Success 200 {object} types.GeneralResponse{data=types.AcceptGroupApplyResp} +// @Router /app/accept-group-apply [post] +func AcceptGroupApply(c *gin.Context) { + userId, ok := c.Get(api.Address) + if !ok { + c.Set(api.ReqError, xerror.NewError(xerror.SignatureInvalid)) + return + } + req := &types.AcceptGroupApplyReq{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + req.PersonId = userId.(string) + + res, err := svc.AcceptGroupApplySvc(c, req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) + +} + +// RejectGroupApply +// @Summary 拒绝加群审批 +// @Author chy@33.cn +// @Tags group 群审批 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.RejectGroupApplyReq false "body" +// @Success 200 {object} types.GeneralResponse{data=types.RejectGroupApplyResp} +// @Router /app/reject-group-apply [post] +func RejectGroupApply(c *gin.Context) { + userId, ok := c.Get(api.Address) + if !ok { + c.Set(api.ReqError, xerror.NewError(xerror.SignatureInvalid)) + return + } + req := &types.RejectGroupApplyReq{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + req.PersonId = userId.(string) + + res, err := svc.RejectGroupApplySvc(c, req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) + +} + +// GetGroupApplyById +// @Summary 查询加群审批 +// @Author chy@33.cn +// @Tags group 群审批 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.GetGroupApplyByIdReq false "body" +// @Success 200 {object} types.GeneralResponse{data=types.GetGroupApplysResp} +// @Router /app/get-group-apply [post] +func GetGroupApplyById(c *gin.Context) { + userId, ok := c.Get(api.Address) + if !ok { + c.Set(api.ReqError, xerror.NewError(xerror.SignatureInvalid)) + return + } + req := &types.GetGroupApplyByIdReq{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + req.PersonId = userId.(string) + + res, err := svc.GetGroupApplyByIdSvc(c, req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) + +} + +// GetGroupApplys +// @Summary 查询加群审批列表 +// @Author chy@33.cn +// @Tags group 群审批 +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body types.GetGroupApplysReq false "body" +// @Success 200 {object} types.GeneralResponse{data=types.GetGroupApplysResp} +// @Router /app/get-group-applys [post] +func GetGroupApplys(c *gin.Context) { + userId, ok := c.Get(api.Address) + if !ok { + c.Set(api.ReqError, xerror.NewError(xerror.SignatureInvalid)) + return + } + req := &types.GetGroupApplysReq{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + req.PersonId = userId.(string) + + res, err := svc.GetGroupApplysSvc(c, req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) + +} diff --git a/service/group/server/http/http.go b/service/group/server/http/http.go new file mode 100644 index 0000000..09f6357 --- /dev/null +++ b/service/group/server/http/http.go @@ -0,0 +1,110 @@ +package http + +import ( + "net/http" + + "gitlab.33.cn/chat/dtalk/pkg/api/logger" + "gitlab.33.cn/chat/dtalk/pkg/api/trace" + + "github.com/gin-gonic/gin" + "github.com/inconshreveable/log15" + swaggerFiles "github.com/swaggo/files" + ginSwagger "github.com/swaggo/gin-swagger" + "gitlab.33.cn/chat/dtalk/pkg/api" + _ "gitlab.33.cn/chat/dtalk/service/group/docs" + "gitlab.33.cn/chat/dtalk/service/group/service" +) + +var ( + svc *service.Service + log = log15.New("module", "group/http") +) + +func Init(s *service.Service) *http.Server { + addr := s.Config().HttpServer.Addr + if s.Config().Env != "debug" { + gin.SetMode(gin.ReleaseMode) + } + engine := Default() + InitService(s) + SetupEngine(engine) + + srv := &http.Server{ + Addr: addr, + Handler: engine, + } + go func() { + // service connections + if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Error("", "engineInner.Start() error(%v)", err) + panic(err) + } + }() + return srv +} + +// Default returns an Engine instance with the Logger and Recovery middleware already attached. +func Default() *gin.Engine { + router := gin.New() + // LoggerWithFormatter middleware will write the logs to gin.DefaultWriter + // By default gin.DefaultWriter = os.Stdout + //router.Use(gin.LoggerWithFormatter(api.Chat33GinLogFormatter)) + router.Use(gin.Recovery()) + return router +} + +func InitService(s *service.Service) { + svc = s +} + +// SetupEngine +// @title 群服务接口 +// @version 1.0 +// @host 127.0.0.1:18011 +func SetupEngine(e *gin.Engine) *gin.Engine { + group := e.Group("/app", api.RespMiddleWare()) + logMiddleware := logger.NewMiddleware(svc.GetLog()) + group.Use(api.AuthMiddleWare(), trace.TraceMiddleware(), logMiddleware.Handle()) + { + group.POST("/create-group", CreateGroup) + group.POST("/invite-group-members", InviteGroupMembers) + group.POST("/group-exit", GroupExit) + group.POST("/group-disband", GroupDisband) + group.POST("/group-remove", GroupRemove) + group.POST("/change-owner", ChangeOwner) + group.POST("/join-group", JoinGroup) + + group.POST("/name", UpdateGroupName) + group.POST("/avatar", UpdateGroupAvatar) + group.POST("/joinType", UpdateGroupJoinType) + group.POST("/friendType", UpdateGroupFriendType) + group.POST("/muteType", UpdateGroupMuteType) + group.POST("/member/name", UpdateGroupMemberName) + group.POST("/member/type", SetAdmin) + group.POST("/member/muteTime", SetMembersMuteTime) + + //group.GET("/group/:id", GetGroupInfoById) + group.POST("/group-info", GetGroupInfo) + group.POST("/group-pub-info", GetGroupPubInfo) + //group.GET("/groups", GetGroups) + group.POST("/group-list", GetGroupList) + group.POST("/group-search", GetGroupInfoByCondition) + //group.GET("/group/:id/members", GetGroupMemberListByUri) + group.POST("/group-member-list", GetGroupMemberList) + //group.GET("/group/:id/member/:memberId", GetGroupMemberInfoByUri) + group.POST("/group-member-info", GetGroupMemberInfo) + group.POST("/mute-list", GetMuteList) + + group.POST("/create-group-apply", CreateGroupApply) + group.POST("/accept-group-apply", AcceptGroupApply) + group.POST("/reject-group-apply", RejectGroupApply) + group.POST("/get-group-apply", GetGroupApplyById) + group.POST("/get-group-applys", GetGroupApplys) + } + + // prometheus 接口 + //e.GET("/prometheus", midware.PrometheusHandler()) + // swagger 文档接口 + e.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) + return e +} diff --git a/service/group/service/acceptgroupapply.go b/service/group/service/acceptgroupapply.go new file mode 100644 index 0000000..cf023c4 --- /dev/null +++ b/service/group/service/acceptgroupapply.go @@ -0,0 +1,96 @@ +package service + +import ( + "context" + + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/service/group/model/biz" + "gitlab.33.cn/chat/dtalk/service/group/model/db" + "gitlab.33.cn/chat/dtalk/service/group/model/types" + "gitlab.33.cn/utils/go-kit/convert" +) + +// AcceptGroupApplySvc 接受 申请加入团队 的审批 +func (s *Service) AcceptGroupApplySvc(ctx context.Context, req *types.AcceptGroupApplyReq) (res *types.AcceptGroupApplyResp, err error) { + //log := s.GetLogWithTrace(ctx) + + personId := req.PersonId + groupId := convert.ToInt64(req.Id) + applyId := convert.ToInt64(req.ApplyId) + + // 查询审批详情 + groupApply, err := s.getGroupApplyById(applyId) + if err != nil { + return nil, err + } + // 判断审批是否被处理过 + if err = groupApply.IsWait(); err != nil { + return nil, err + } + + // 查询操作人详情并判断是否有权限操作 + person, err := s.GetPersonByMemberIdAndGroupId(ctx, personId, groupId) + if err != nil { + return nil, err + } + if err = person.IsAdmin(); err != nil { + return nil, err + } + + // 查询群详情 + group, err := s.GetGroupInfoByGroupId(ctx, groupId) + if err != nil { + return nil, err + } + // 判断群人数上限 + if err = group.TryJoin(1); err != nil { + return nil, err + } + + // 处理审批 + groupApply.ApplyStatus = biz.GroupApplyAccept + groupApply.OperatorId = personId + err = s.updateGroupApply(groupApply) + if err != nil { + return nil, err + } + + // 判断是否已经在群内 + if isExit, err := s.CheckInGroup(groupApply.MemberId, groupId); err != nil { + return nil, err + } else if isExit == true { + err = xerror.NewError(xerror.GroupInviteMemberExist) + return nil, err + } + + // 加人 + nowTime := s.getNowTime() + newMember := &db.GroupMember{ + GroupId: groupId, + GroupMemberId: groupApply.MemberId, + GroupMemberName: "", + GroupMemberType: 0, + GroupMemberJoinTime: nowTime, + GroupMemberUpdateTime: nowTime, + } + + opeId := groupApply.InviterId + if opeId == "" { + opeId = groupApply.OperatorId + } + + if err = s.AddGroupMembers(ctx, group.GroupId, []*db.GroupMember{newMember}, opeId); err != nil { + return nil, err + } + + return &types.AcceptGroupApplyResp{}, nil +} + +func (s *Service) updateGroupApply(biz *biz.GroupApplyBiz) error { + err := s.dao.UpdateGroupApply(biz.OperatorId, biz.ApplyStatus, biz.RejectReason, biz.ApplyId) + if err != nil { + return err + } + + return nil +} diff --git a/service/group/service/answerAlert.go b/service/group/service/answerAlert.go new file mode 100644 index 0000000..ea9929f --- /dev/null +++ b/service/group/service/answerAlert.go @@ -0,0 +1,240 @@ +package service + +import ( + "context" + + "gitlab.33.cn/utils/go-kit/convert" + + "github.com/golang/protobuf/proto" + "github.com/google/uuid" + "gitlab.33.cn/chat/dtalk/service/group/model" + answer "gitlab.33.cn/chat/dtalk/service/record/answer/api" + xproto "gitlab.33.cn/chat/imparse/proto" +) + +func (s *Service) PushNoticeMsg(ctx context.Context, channelType int32, from, target string, data []byte) error { + noticeMsgReq := &answer.PushNoticeMsgReq{ + Seq: uuid.New().String(), + ChannelType: channelType, + From: from, + Target: target, + Data: data, + } + + if _, err := s.answerClient.PushNoticeMsg( + ctx, noticeMsgReq.Seq, noticeMsgReq.ChannelType, + noticeMsgReq.From, noticeMsgReq.Target, noticeMsgReq.Data); err != nil { + return err + } + return nil +} + +func (s *Service) PushGroupNoticeMsg(ctx context.Context, groupId int64, personId string, noticeMsgMsg *xproto.NoticeMsg) error { + data, err := proto.Marshal(noticeMsgMsg) + if err != nil { + return err + } + from := personId + target := convert.ToString(groupId) + + if err = s.PushNoticeMsg(ctx, 1, from, target, data); err != nil { + return err + } + return nil +} + +func convertNoticeMsgMsg(noticeMsgType xproto.NoticeMsgType, noticeMsg proto.Message) (*xproto.NoticeMsg, error) { + body, err := proto.Marshal(noticeMsg) + if err != nil { + return nil, err + } + noticeMsgMsg := &xproto.NoticeMsg{ + Type: noticeMsgType, + Body: body, + } + return noticeMsgMsg, nil +} + +func (s *Service) NoticeMsgUpdateGroupName(ctx context.Context, groupId int64, personId string, name string) error { + if name == "" { + return nil + } + + noticeMsgType := xproto.NoticeMsgType_UpdateGroupNameNoticeMsg + noticeMsg := &xproto.NoticeMsgUpdateGroupName{ + Group: groupId, + Operator: personId, + Name: name, + } + noticeMsgMsg, err := convertNoticeMsgMsg(noticeMsgType, noticeMsg) + if err != nil { + return err + } + + if err = s.PushGroupNoticeMsg(ctx, groupId, personId, noticeMsgMsg); err != nil { + return err + } + return nil +} + +func (s *Service) NoticeMsgSignInGroup(ctx context.Context, groupId int64, personId string, memberIds []string) error { + noticeMsgType := xproto.NoticeMsgType_SignInGroupNoticeMsg + noticeMsg := &xproto.NoticeMsgSignInGroup{ + Group: groupId, + Inviter: personId, + Members: memberIds, + } + noticeMsgMsg, err := convertNoticeMsgMsg(noticeMsgType, noticeMsg) + if err != nil { + return err + } + + if err = s.PushGroupNoticeMsg(ctx, groupId, personId, noticeMsgMsg); err != nil { + return err + } + return nil +} + +func (s *Service) NoticeMsgSignOutGroup(ctx context.Context, groupId int64, personId string) error { + noticeMsgType := xproto.NoticeMsgType_SignOutGroupNoticeMsg + noticeMsg := &xproto.NoticeMsgSignOutGroup{ + Group: groupId, + Operator: personId, + } + body, err := proto.Marshal(noticeMsg) + if err != nil { + return err + } + + noticeMsgMsg := &xproto.NoticeMsg{ + Type: noticeMsgType, + Body: body, + } + + if err = s.PushGroupNoticeMsg(ctx, groupId, personId, noticeMsgMsg); err != nil { + return err + } + return nil +} + +func (s *Service) NoticeMsgKickOutGroup(ctx context.Context, groupId int64, personId string, memberIds []string) error { + noticeMsgType := xproto.NoticeMsgType_KickOutGroupNoticeMsg + noticeMsg := &xproto.NoticeMsgKickOutGroup{ + Group: groupId, + Operator: personId, + Members: memberIds, + } + body, err := proto.Marshal(noticeMsg) + if err != nil { + return err + } + + noticeMsgMsg := &xproto.NoticeMsg{ + Type: noticeMsgType, + Body: body, + } + + if err = s.PushGroupNoticeMsg(ctx, groupId, personId, noticeMsgMsg); err != nil { + return err + } + return nil +} + +func (s *Service) NoticeMsgDeleteGroup(ctx context.Context, groupId int64, personId string) error { + noticeMsgType := xproto.NoticeMsgType_DeleteGroupNoticeMsg + noticeMsg := &xproto.NoticeMsgDeleteGroup{ + Group: groupId, + Operator: personId, + } + body, err := proto.Marshal(noticeMsg) + if err != nil { + return err + } + + noticeMsgMsg := &xproto.NoticeMsg{ + Type: noticeMsgType, + Body: body, + } + + if err = s.PushGroupNoticeMsg(ctx, groupId, personId, noticeMsgMsg); err != nil { + return err + } + return nil +} + +func (s *Service) NoticeMsgUpdateGroupMuted(ctx context.Context, groupId int64, personId string, muteType int32) error { + var proMuteType xproto.MuteType + switch muteType { + case int32(xproto.MuteType_MuteAllow): + proMuteType = xproto.MuteType_MuteAllow + case int32(xproto.MuteType_MuteDeny): + proMuteType = xproto.MuteType_MuteDeny + default: + return model.ErrType + } + noticeMsgType := xproto.NoticeMsgType_UpdateGroupMutedNoticeMsg + noticeMsg := &xproto.NoticeMsgUpdateGroupMuted{ + Group: groupId, + Operator: personId, + Type: proMuteType, + } + body, err := proto.Marshal(noticeMsg) + if err != nil { + return err + } + + noticeMsgMsg := &xproto.NoticeMsg{ + Type: noticeMsgType, + Body: body, + } + + if err = s.PushGroupNoticeMsg(ctx, groupId, personId, noticeMsgMsg); err != nil { + return err + } + return nil +} + +func (s *Service) NoticeMsgUpdateGroupMemberMutedTime(ctx context.Context, groupId int64, personId string, memberIds []string) error { + noticeMsgType := xproto.NoticeMsgType_UpdateGroupMemberMutedNoticeMsg + noticeMsg := &xproto.NoticeMsgUpdateGroupMemberMutedTime{ + Group: groupId, + Operator: personId, + Members: memberIds, + } + body, err := proto.Marshal(noticeMsg) + if err != nil { + return err + } + + noticeMsgMsg := &xproto.NoticeMsg{ + Type: noticeMsgType, + Body: body, + } + + if err = s.PushGroupNoticeMsg(ctx, groupId, personId, noticeMsgMsg); err != nil { + return err + } + return nil +} + +func (s *Service) NoticeMsgUpdateGroupOwner(ctx context.Context, groupId int64, personId, newOwner string) error { + noticeMsgType := xproto.NoticeMsgType_UpdateGroupOwnerNoticeMsg + noticeMsg := &xproto.NoticeMsgUpdateGroupOwner{ + Group: groupId, + NewOwner: newOwner, + } + body, err := proto.Marshal(noticeMsg) + if err != nil { + return err + } + + noticeMsgMsg := &xproto.NoticeMsg{ + Type: noticeMsgType, + Body: body, + } + + if err = s.PushGroupNoticeMsg(ctx, groupId, personId, noticeMsgMsg); err != nil { + return err + } + return nil +} diff --git a/service/group/service/answerGroupNotice.go b/service/group/service/answerGroupNotice.go new file mode 100644 index 0000000..dafba80 --- /dev/null +++ b/service/group/service/answerGroupNotice.go @@ -0,0 +1,288 @@ +package service + +import ( + "context" + + "github.com/golang/protobuf/proto" + "gitlab.33.cn/chat/dtalk/service/group/model" + xproto "gitlab.33.cn/chat/imparse/proto" + "gitlab.33.cn/utils/go-kit/convert" +) + +func (s *Service) PusherSignalJoin(ctx context.Context, groupId int64, groupMemberIds []string) error { + nowTime := s.getNowTime() + actionSignInGroup := &xproto.SignalSignInGroup{ + Uid: groupMemberIds, + Group: groupId, + Time: convert.ToUint64(nowTime), + } + body, err := proto.Marshal(actionSignInGroup) + if err != nil { + return err + } + + Signal := xproto.SignalType_SignInGroup + Target := convert.ToString(groupId) + Body := body + + if err = s.answerClient.GroupCastSignal(ctx, Signal, Target, Body); err != nil { + return err + } + return nil +} + +func (s *Service) PusherSignalLeave(ctx context.Context, groupId int64, groupMemberIds []string) error { + nowTime := s.getNowTime() + actionSignOutGroup := &xproto.SignalSignOutGroup{ + Uid: groupMemberIds, + Group: groupId, + Time: convert.ToUint64(nowTime), + } + body, err := proto.Marshal(actionSignOutGroup) + if err != nil { + return err + } + + Signal := xproto.SignalType_SignOutGroup + Target := convert.ToString(groupId) + Body := body + + if err = s.answerClient.GroupCastSignal(ctx, Signal, Target, Body); err != nil { + return err + } + return nil +} + +func (s *Service) PusherSignalDel(ctx context.Context, groupId int64) error { + nowTime := s.getNowTime() + actionDealeteGroup := &xproto.SignalDeleteGroup{ + Group: groupId, + Time: convert.ToUint64(nowTime), + } + body, err := proto.Marshal(actionDealeteGroup) + if err != nil { + return err + } + + Signal := xproto.SignalType_DeleteGroup + Target := convert.ToString(groupId) + Body := body + + if err = s.answerClient.GroupCastSignal(ctx, Signal, Target, Body); err != nil { + return err + } + return nil +} + +func (s *Service) PusherSignalJoinType(ctx context.Context, groupId int64, joinType int32) error { + nowTime := s.getNowTime() + + var proJoinType xproto.JoinType + + switch joinType { + case int32(xproto.JoinType_JoinAllow): + proJoinType = xproto.JoinType_JoinAllow + case int32(xproto.JoinType_JoinDeny): + proJoinType = xproto.JoinType_JoinDeny + case int32(xproto.JoinType_JoinApply): + proJoinType = xproto.JoinType_JoinApply + + default: + return model.ErrType + } + + action := &xproto.SignalUpdateGroupJoinType{ + Group: groupId, + Type: proJoinType, + Time: convert.ToUint64(nowTime), + } + body, err := proto.Marshal(action) + if err != nil { + return err + } + + Signal := xproto.SignalType_UpdateGroupJoinType + Target := convert.ToString(groupId) + Body := body + + if err = s.answerClient.GroupCastSignal(ctx, Signal, Target, Body); err != nil { + return err + } + return nil +} + +func (s *Service) PusherSignalFriendType(ctx context.Context, groupId int64, friendType int32) error { + nowTime := s.getNowTime() + + var proFriendType xproto.FriendType + + switch friendType { + case int32(xproto.FriendType_FriendAllow): + proFriendType = xproto.FriendType_FriendAllow + case int32(xproto.FriendType_FriendDeny): + proFriendType = xproto.FriendType_FriendDeny + default: + return model.ErrType + } + + action := &xproto.SignalUpdateGroupFriendType{ + Group: groupId, + Type: proFriendType, + Time: convert.ToUint64(nowTime), + } + body, err := proto.Marshal(action) + if err != nil { + return err + } + + Signal := xproto.SignalType_UpdateGroupFriendType + Target := convert.ToString(groupId) + Body := body + + if err = s.answerClient.GroupCastSignal(ctx, Signal, Target, Body); err != nil { + return err + } + return nil +} + +func (s *Service) PusherSignalMuteType(ctx context.Context, groupId int64, muteType int32) error { + nowTime := s.getNowTime() + + var proMuteType xproto.MuteType + + switch muteType { + case int32(xproto.MuteType_MuteAllow): + proMuteType = xproto.MuteType_MuteAllow + case int32(xproto.MuteType_MuteDeny): + proMuteType = xproto.MuteType_MuteDeny + default: + return model.ErrType + } + + action := &xproto.SignalUpdateGroupMuteType{ + Group: groupId, + Type: proMuteType, + Time: convert.ToUint64(nowTime), + } + body, err := proto.Marshal(action) + if err != nil { + return err + } + + Signal := xproto.SignalType_UpdateGroupMuteType + Target := convert.ToString(groupId) + Body := body + + if err = s.answerClient.GroupCastSignal(ctx, Signal, Target, Body); err != nil { + return err + } + return nil +} + +func (s *Service) PusherSignalMemberType(ctx context.Context, groupId int64, memberId string, memberType int32) error { + nowTime := s.getNowTime() + + var proMemberType xproto.MemberType + + switch memberType { + case int32(xproto.MemberType_Normal): + proMemberType = xproto.MemberType_Normal + case int32(xproto.MemberType_Admin): + proMemberType = xproto.MemberType_Admin + case int32(xproto.MemberType_Owner): + proMemberType = xproto.MemberType_Owner + default: + return model.ErrType + } + + action := &xproto.SignalUpdateGroupMemberType{ + Group: groupId, + Type: proMemberType, + Uid: memberId, + Time: convert.ToUint64(nowTime), + } + body, err := proto.Marshal(action) + if err != nil { + return err + } + + Signal := xproto.SignalType_UpdateGroupMemberType + Target := convert.ToString(groupId) + Body := body + + if err = s.answerClient.GroupCastSignal(ctx, Signal, Target, Body); err != nil { + return err + } + return nil +} + +func (s *Service) PusherSignalMemberMuteTime(ctx context.Context, groupId int64, memberIds []string, muteTime int64) error { + nowTime := s.getNowTime() + action := &xproto.SignalUpdateGroupMemberMuteTime{ + Group: groupId, + Uid: memberIds, + MuteTime: muteTime, + Time: convert.ToUint64(nowTime), + } + body, err := proto.Marshal(action) + if err != nil { + return err + } + + Signal := xproto.SignalType_UpdateGroupMemberMuteTime + Target := convert.ToString(groupId) + Body := body + + if err = s.answerClient.GroupCastSignal(ctx, Signal, Target, Body); err != nil { + return err + } + return nil +} + +func (s *Service) PusherSignalGroupName(ctx context.Context, groupId int64, name string) error { + if name == "" { + return nil + } + + nowTime := s.getNowTime() + action := &xproto.SignalUpdateGroupName{ + Group: groupId, + Name: name, + Time: convert.ToUint64(nowTime), + } + body, err := proto.Marshal(action) + if err != nil { + return err + } + + Signal := xproto.SignalType_UpdateGroupName + Target := convert.ToString(groupId) + Body := body + + if err = s.answerClient.GroupCastSignal(ctx, Signal, Target, Body); err != nil { + return err + } + return nil +} + +func (s *Service) PusherSignalGroupAvatar(ctx context.Context, groupId int64, avatar string) error { + nowTime := s.getNowTime() + action := &xproto.SignalUpdateGroupAvatar{ + Group: groupId, + Avatar: avatar, + Time: convert.ToUint64(nowTime), + } + body, err := proto.Marshal(action) + if err != nil { + return err + } + + Signal := xproto.SignalType_UpdateGroupAvatar + Target := convert.ToString(groupId) + Body := body + + if err = s.answerClient.GroupCastSignal(ctx, Signal, Target, Body); err != nil { + return err + } + return nil +} diff --git a/service/group/service/bizcommon.go b/service/group/service/bizcommon.go new file mode 100644 index 0000000..9098714 --- /dev/null +++ b/service/group/service/bizcommon.go @@ -0,0 +1,35 @@ +package service + +// 组合各种数据访问来构建业务逻辑。 + +import ( + "gitlab.33.cn/chat/dtalk/pkg/mysql" + "gitlab.33.cn/chat/dtalk/service/group/model/biz" + "gitlab.33.cn/chat/dtalk/service/group/model/db" + "time" +) + +func (s *Service) getNowTime() int64 { + return time.Now().UnixNano() / 1e6 +} + +// CheckInGroup 查询并检查该成员是否在群中 +func (s *Service) CheckInGroup(memberId string, groupId int64) (bool, error) { + memberType, err := s.dao.GetMemberTypeMemberIdAndGroupId(memberId, groupId) + if err != nil { + return false, err + } + if memberType == biz.GroupMemberTypeOther { + return false, nil + } + return true, nil +} + +// InsertGroupMembers 批量插入群成员 +func (s *Service) InsertGroupMembers(tx *mysql.MysqlTx, groupMembers []*db.GroupMember) error { + err := s.dao.InsertGroupMembers(tx, groupMembers) + if err != nil { + return err + } + return nil +} diff --git a/service/group/service/bizgroupinfo.go b/service/group/service/bizgroupinfo.go new file mode 100644 index 0000000..5e60d8e --- /dev/null +++ b/service/group/service/bizgroupinfo.go @@ -0,0 +1,93 @@ +package service + +import ( + "context" + "errors" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/service/group/model" + "gitlab.33.cn/chat/dtalk/service/group/model/biz" + "gitlab.33.cn/chat/dtalk/service/group/model/db" +) + +// GetGroupInfoByGroupId 根据 GroupId 查询群信息 +func (s *Service) GetGroupInfoByGroupId(ctx context.Context, groupId int64) (*biz.GroupInfo, error) { + groupInfo, err := s.dao.GetGroupInfoByGroupId(ctx, groupId) + if err != nil { + if errors.Is(err, model.ErrRecordNotExist) { + return nil, xerror.NewError(xerror.GroupNotExist) + } + return nil, err + } + + if err = groupInfo.IsNormal(); err != nil { + return nil, err + } + + return groupInfo, nil +} + +// GetGroupInfoByGroupMarkId 根据 GroupMarkId 查询群信息 +// 没用到 +func (s *Service) GetGroupInfoByGroupMarkId(groupMarkId string) (*biz.GroupInfo, error) { + groupInfo, err := s.dao.GetGroupInfoByGroupMarkId(groupMarkId) + if err != nil { + if errors.Is(err, model.ErrRecordNotExist) { + return nil, xerror.NewError(xerror.GroupNotExist) + } + return nil, err + } + + return groupInfo, nil +} + +// UpdateGroupInfoMemberNum 更新群成员数量 +func (s *Service) UpdateGroupInfoMemberNum(ctx context.Context, groupId int64) (int32, error) { + n, err := s.dao.UpdateGroupInfoMemberNum(ctx, groupId) + if err != nil { + return 0, err + } + return n, nil +} + +// GetGroupIdsByMemberId 查询用户所有加入的群 ID +func (s *Service) GetGroupIdsByMemberId(memberId string) ([]int64, error) { + groupIds, err := s.dao.GetGroupIdsByMemberId(memberId) + if err != nil { + return nil, err + } + return groupIds, nil +} + +// GetGroupsByGroupIds 查询用户所有加入的群 +// todo: 有 bug +func (s *Service) GetGroupsByGroupIds(ctx context.Context, groupIds []int64) ([]*biz.GroupInfo, error) { + groups, err := s.dao.GetGroupInfosByGroupIds(ctx, groupIds) + if err != nil { + return nil, err + } + + return groups, nil +} + +// CheckGroupMemberNum 检查新增群员是否会超过群人数上限 +func (s *Service) CheckGroupMemberNum(newMembers int32, groupMaximum int32) error { + if newMembers > groupMaximum { + return xerror.NewError(xerror.GroupMemberLimit) + } + return nil +} + +// UpdateGroupType 更新群类型 +// rpc 专用 +func (s *Service) UpdateGroupType(groupId int64, groupType int32) error { + // todo check groupType + _, _, err := s.dao.MaintainGroupType(&db.GroupInfo{ + GroupId: groupId, + GroupType: groupType, + }) + if err != nil { + return err + } + + return nil +} diff --git a/service/group/service/bizgroupmember.go b/service/group/service/bizgroupmember.go new file mode 100644 index 0000000..97ed1fa --- /dev/null +++ b/service/group/service/bizgroupmember.go @@ -0,0 +1,90 @@ +package service + +import ( + "context" + + "github.com/pkg/errors" + "gitlab.33.cn/chat/dtalk/pkg/contextx" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/service/group/model" + "gitlab.33.cn/chat/dtalk/service/group/model/biz" + "gitlab.33.cn/chat/dtalk/service/group/model/db" +) + +// GetMemberByMemberIdAndGroupId 查询对方在群里的信息 +func (s *Service) GetMemberByMemberIdAndGroupId(ctx context.Context, memberId string, groupId int64) (*biz.GroupMember, error) { + member, err := s.dao.GetGroupMemberByGroupIdAndMemberId(ctx, groupId, memberId) + if err != nil { + if errors.Is(err, model.ErrRecordNotExist) { + return nil, xerror.NewError(xerror.GroupMemberNotExist) + } + return nil, err + } + + return member, nil +} + +// GetPersonByMemberIdAndGroupId 查询本人在群里的信息 +func (s *Service) GetPersonByMemberIdAndGroupId(ctx context.Context, memberId string, groupId int64) (*biz.GroupMember, error) { + member, err := s.dao.GetGroupMemberByGroupIdAndMemberId(ctx, groupId, memberId) + if err != nil { + if errors.Is(err, model.ErrRecordNotExist) { + return nil, xerror.NewError(xerror.GroupPersonNotExist) + } + return nil, err + } + + return member, nil +} + +// GetGroupMembersByGroupIdWithLimit 查询群内前 n 个群成员信息 +func (s *Service) GetGroupMembersByGroupIdWithLimit(groupId, n, m int64) ([]*biz.GroupMember, error) { + members, err := s.dao.GetGroupMembersByGroupIdWithLimit(groupId, n, m) + if err != nil { + return nil, err + } + return members, nil +} + +// GetMembersByGroupId 查询群里的所有成员信息 +func (s *Service) GetMembersByGroupId(groupId int64) ([]*biz.GroupMember, error) { + res, err := s.dao.GetMembersByGroupId(groupId) + if err != nil { + return nil, err + } + return res, nil +} + +// GetGroupMemberMuteTime 查询群成员禁言时间 +func (s *Service) GetGroupMemberMuteTime(groupId int64, memberId string) (int64, error) { + muteTime, err := s.dao.GetGroupMemberMuteTime(groupId, memberId) + if err != nil { + return 0, err + } + return muteTime, nil +} + +// AddGroupMembers 加群并发送通知 +func (s *Service) AddGroupMembers(ctx context.Context, groupId int64, members []*db.GroupMember, opeId string) error { + log := s.GetLogWithTrace(ctx) + + if err := s.ExecJoinGroupMembers(members); err != nil { + return err + } + + // 更新群人数 + _, err := s.UpdateGroupInfoMemberNum(ctx, groupId) + if err != nil { + // todo + log.Error().Err(err).Msg("AddGroupMembers") + } + + memberIds := make([]string, 0, len(members)) + for _, member := range members { + memberIds = append(memberIds, member.GroupMemberId) + } + + go s.NoticeInviteMembers(contextx.ValueOnlyFrom(ctx), groupId, opeId, memberIds) + + return nil +} diff --git a/service/group/service/bizlogic.go b/service/group/service/bizlogic.go new file mode 100644 index 0000000..79a02f6 --- /dev/null +++ b/service/group/service/bizlogic.go @@ -0,0 +1,83 @@ +package service + +import ( + "context" + "fmt" + "gitlab.33.cn/utils/go-kit/convert" + "time" + + "github.com/pkg/errors" + "gitlab.33.cn/chat/dtalk/service/group/model" + logic "gitlab.33.cn/chat/im/api/logic/grpc" +) + +func (s *Service) LogicNoticeJoin(ctx context.Context, groupId int64, groupMemberIds []string) error { + gid := make([]string, 1, 1) + gid[0] = convert.ToString(groupId) + + groupMid := &logic.GroupsMid{ + AppId: s.cfg.AppId, + Gid: gid, + Mids: groupMemberIds, + } + + reply, err := s.logicClient.JoinGroupsByMids(ctx, groupMid) + if err != nil { + if err.Error() == model.ErrPushMsgArg.Error() { + return nil + } + return err + } else if reply.IsOk == false { + return errors.New(fmt.Sprintf("reply=%+v", reply)) + } + + return nil +} + +func (s *Service) LogicNoticeLeave(ctx context.Context, groupId int64, groupMemberIds []string) error { + // 保证离开前的通知收到 + time.Sleep(3 * time.Second) + + gid := make([]string, 1, 1) + gid[0] = convert.ToString(groupId) + + groupMid := &logic.GroupsMid{ + AppId: s.cfg.AppId, + Gid: gid, + Mids: groupMemberIds, + } + + reply, err := s.logicClient.LeaveGroupsByMids(ctx, groupMid) + if err != nil { + if err.Error() == model.ErrPushMsgArg.Error() { + return nil + } + return err + } else if reply.IsOk == false { + return errors.New(fmt.Sprintf("reply=%+v", reply)) + } + + return nil +} + +func (s *Service) LogicNoticeDel(ctx context.Context, groupId int64) error { + // 保证离开前的通知收到 + time.Sleep(3 * time.Second) + + gid := make([]string, 1, 1) + gid[0] = convert.ToString(groupId) + + delGroupsReq := &logic.DelGroupsReq{ + AppId: s.cfg.AppId, + Gid: gid, + } + + reply, err := s.logicClient.DelGroups(ctx, delGroupsReq) + if err != nil { + return err + } else if reply.IsOk == false { + return errors.New(fmt.Sprintf("reply=%+v", reply)) + } + + return nil +} diff --git a/service/group/service/biznotice.go b/service/group/service/biznotice.go new file mode 100644 index 0000000..56d4da9 --- /dev/null +++ b/service/group/service/biznotice.go @@ -0,0 +1,24 @@ +package service + +import "context" + +// NoticeInviteMembers 新成员加入群发起通知 +func (s *Service) NoticeInviteMembers(ctx context.Context, groupId int64, inviterId string, newMembers []string) { + log := s.GetLogWithTrace(ctx) + + // 通知 logic + if err := s.LogicNoticeJoin(ctx, groupId, newMembers); err != nil { + log.Error().Err(err).Msg("inviteMemberNotice logic") + } + + // 发送给 pusher + if err := s.PusherSignalJoin(ctx, groupId, newMembers); err != nil { + log.Error().Err(err).Msg("inviteMemberNotice pusher") + } + + if err := s.NoticeMsgSignInGroup(ctx, groupId, inviterId, newMembers); err != nil { + log.Error().Err(err).Msg("inviteMemberNotice alert") + } +} + +// diff --git a/service/group/service/changeowner.go b/service/group/service/changeowner.go new file mode 100644 index 0000000..0f0dfcb --- /dev/null +++ b/service/group/service/changeowner.go @@ -0,0 +1,108 @@ +package service + +import ( + "context" + + "gitlab.33.cn/chat/dtalk/pkg/contextx" + + "gitlab.33.cn/chat/dtalk/service/group/model/biz" + "gitlab.33.cn/chat/dtalk/service/group/model/db" +) + +func (s *Service) ChangeOwner(ctx context.Context, group *biz.GroupInfo, owner, member *biz.GroupMember) error { + groupId := group.GroupId + ownerId := owner.GroupMemberId + memberId := member.GroupMemberId + var err error + log := s.GetLogWithTrace(ctx) + + if err = s.ExecChangeOwner(groupId, ownerId, memberId); err != nil { + return err + } + + go func() { + // 发送给 pusher + if err = s.PusherSignalMemberType(contextx.ValueOnlyFrom(ctx), groupId, memberId, biz.GroupMemberTypeOwner); err != nil { + log.Error().Err(err).Msg("ChangeOwnerHttp pusher") + } + + // 发送给 pusher + if err = s.PusherSignalMemberType(contextx.ValueOnlyFrom(ctx), groupId, ownerId, biz.GroupMemberTypeNormal); err != nil { + log.Error().Err(err).Msg("ChangeOwnerHttp pusher") + } + + // 发送给 alert + if err = s.NoticeMsgUpdateGroupOwner(contextx.ValueOnlyFrom(ctx), groupId, ownerId, memberId); err != nil { + log.Error().Err(err).Msg("ChangeOwnerHttp alert") + } + }() + + // 解除新群主禁言 + nowTime := s.getNowTime() + groupMemberMute := make([]*db.GroupMemberMute, 1, 1) + groupMemberMute[0] = &db.GroupMemberMute{ + GroupId: groupId, + GroupMemberId: memberId, + GroupMemberMuteTime: 0, + GroupMemberMuteUpdateTime: nowTime, + } + if err = s.execSetMemberMuteTimes(ctx, groupMemberMute); err != nil { + return err + } + + // 发送给 pusher + if err = s.PusherSignalMemberMuteTime(contextx.ValueOnlyFrom(ctx), groupId, []string{memberId}, 0); err != nil { + log.Error().Err(err).Msg("UpdateMembersMuteTimeSvc pusher") + } + + return nil +} + +// ExecChangeOwner 转让群主 +func (s *Service) ExecChangeOwner(groupId int64, ownerId, memberId string) error { + // todo + // 如果并发换群主可能出现两个群主的情况 + nowTime := s.getNowTime() + tx, err := s.dao.NewTx() + if err != nil { + return err + } + defer tx.RollBack() + + groupInfo := &db.GroupInfo{ + GroupId: groupId, + GroupOwnerId: memberId, + GroupUpdateTime: nowTime, + } + if _, _, err = s.dao.UpdateGroupInfoOwnerIdWithTx(tx, groupInfo); err != nil { + return err + } + + owner := &db.GroupMember{ + GroupId: groupId, + GroupMemberId: ownerId, + GroupMemberType: biz.GroupMemberTypeNormal, + GroupMemberUpdateTime: nowTime, + } + + if _, _, err := s.dao.UpdateGroupMemberTypeWithTx(tx, owner); err != nil { + return err + } + + member := &db.GroupMember{ + GroupId: groupId, + GroupMemberId: memberId, + GroupMemberType: biz.GroupMemberTypeOwner, + GroupMemberUpdateTime: nowTime, + } + + if _, _, err = s.dao.UpdateGroupMemberTypeWithTx(tx, member); err != nil { + return err + } + + if err = tx.Commit(); err != nil { + return err + } + + return nil +} diff --git a/service/group/service/context.go b/service/group/service/context.go new file mode 100644 index 0000000..7a9d21c --- /dev/null +++ b/service/group/service/context.go @@ -0,0 +1,76 @@ +package service + +//type SvcCtxOpt func(ctx context.Context) (context.Context, error) + +//// SetContext 设置业务 ctx, opt 需要按照顺序填写 +//func (s *Service) SetContext(ctx context.Context, options ...SvcCtxOpt) (context.Context, error) { +// var err error +// for _, opt := range options { +// ctx, err = opt(ctx) +// if err != nil { +// return nil, err +// } +// } +// return ctx, nil +//} +// +//func (s *Service) WithGroupInfoCtxOpt(ctx context.Context) (context.Context, error) { +// groupId, err := biz.NewGroupIdFromContext(ctx) +// if err != nil { +// return nil, err +// } +// +// group, err := s.GetGroupInfoByGroupId(ctx, groupId) +// if err != nil { +// return nil, err +// } +// ctx = group.WithContext(ctx) +// +// return ctx, nil +//} +// +//func (s *Service) WithPersonCtxOpt(ctx context.Context) (context.Context, error) { +// groupId, err := biz.NewGroupIdFromContext(ctx) +// if err != nil { +// return nil, err +// } +// +// memberId, _ := ctx.Value(api.Address).(string) +// if memberId == "" { +// return ctx, nil +// } +// +// person, err := s.GetPersonByMemberIdAndGroupId(ctx, memberId, groupId) +// if err != nil { +// return nil, err +// } +// ctx = person.WithContext(ctx) +// +// return ctx, nil +//} +// +//func (s *Service) WithAdminCtxOpt(ctx context.Context) (context.Context, error) { +// person, err := biz.NewGroupMemberFromContext(ctx) +// if err != nil { +// return nil, err +// } +// +// if err := person.IsAdmin(); err != nil { +// return nil, err +// } +// +// return ctx, nil +//} +// +//func (s *Service) WithOwnerCtxOpt(ctx context.Context) (context.Context, error) { +// person, err := biz.NewGroupMemberFromContext(ctx) +// if err != nil { +// return nil, err +// } +// +// if err := person.IsOwner(); err != nil { +// return nil, err +// } +// +// return ctx, nil +//} diff --git a/service/group/service/creategroup.go b/service/group/service/creategroup.go new file mode 100644 index 0000000..47a2ade --- /dev/null +++ b/service/group/service/creategroup.go @@ -0,0 +1,258 @@ +package service + +import ( + "context" + "github.com/pkg/errors" + "gitlab.33.cn/chat/dtalk/pkg/contextx" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + xrand "gitlab.33.cn/chat/dtalk/pkg/rand" + "gitlab.33.cn/chat/dtalk/service/group/model" + "gitlab.33.cn/chat/dtalk/service/group/model/biz" + "gitlab.33.cn/chat/dtalk/service/group/model/db" + "gitlab.33.cn/chat/dtalk/service/group/model/types" + "gitlab.33.cn/utils/go-kit/convert" +) + +//CreateGroupSvc 创建群, 返回群信息 +func (s *Service) CreateGroupSvc(ctx context.Context, req *types.CreateGroupRequest) (res *types.CreateGroupResponse, err error) { + groupId, err := s.getLogId(context.Background()) + if err != nil { + return nil, err + } + + // 判断群人数 + if err := s.CheckGroupMemberNum(convert.ToInt32(len(req.Members))+1, biz.GroupMaximum); err != nil { + return nil, err + } + + // 创建群 + nowTime := s.getNowTime() + + groupMembers := make([]*db.GroupMember, 0, len(req.MemberIds)+1) + groupMembers = append(groupMembers, &db.GroupMember{ + GroupId: groupId, + GroupMemberId: req.Owner.MemberId, + GroupMemberName: req.Owner.MemberName, + GroupMemberType: req.Owner.MemberType, + GroupMemberJoinTime: nowTime, + GroupMemberUpdateTime: nowTime, + }) + for i := 0; i < len(req.MemberIds); i++ { + member := req.Members[i] + if member.MemberId == req.Owner.MemberId { + continue + } + groupMembers = append(groupMembers, &db.GroupMember{ + GroupId: groupId, + GroupMemberId: member.MemberId, + GroupMemberName: member.MemberName, + GroupMemberType: member.MemberType, + GroupMemberJoinTime: nowTime, + GroupMemberUpdateTime: nowTime, + }) + } + + groupMarkId, err := s.getRandomGroupMarkId() + if err != nil { + return nil, err + } + group := &db.GroupInfo{ + GroupId: groupId, + GroupMarkId: groupMarkId, + GroupName: req.Name, + GroupAvatar: req.Avatar, + GroupMemberNum: convert.ToInt32(len(groupMembers)), + GroupMaximum: biz.GroupMaximum, + GroupIntroduce: req.Introduce, + GroupStatus: biz.GroupStatusNormal, + GroupOwnerId: req.Owner.MemberId, + GroupJoinType: biz.GroupJoinTypeAny, + GroupMuteType: biz.GroupMuteTypeAny, + GroupFriendType: biz.GroupFriendTypeAllow, + GroupCreateTime: nowTime, + GroupUpdateTime: nowTime, + GroupAESKey: xrand.NewAESKey256(), + GroupPubName: req.Name, + GroupType: biz.GroupTypeNormal, + } + + err = s.createGroup(ctx, group, groupMembers) + if err != nil { + return nil, err + } + + res = &types.CreateGroupResponse{ + GroupInfo: &types.GroupInfo{ + Id: groupId, + IdStr: convert.ToString(groupId), + MarkId: groupMarkId, + Name: req.Name, + Avatar: req.Avatar, + Introduce: req.Introduce, + Owner: &req.Owner, + MemberNum: convert.ToInt32(1 + len(req.Members)), + Maximum: biz.GroupMaximum, + Status: biz.GroupStatusNormal, + CreateTime: nowTime, + JoinType: biz.GroupJoinTypeAny, + MuteType: biz.GroupMuteTypeAny, + FriendType: biz.GroupFriendTypeAllow, + MuteNum: 0, + PublicName: req.Name, + AESKey: group.GroupAESKey, + GroupType: biz.GroupTypeNormal, + }, + Members: req.Members, + } + return res, nil +} + +// getRandomGroupMarkId 得到不重复的 8 位数字群组 id +func (s *Service) getRandomGroupMarkId() (string, error) { + for { + groupMarkId := xrand.NewNumber(8) + if _, err := s.dao.GetGroupInfoByGroupMarkId(groupMarkId); err != nil { + if errors.Is(err, model.ErrRecordNotExist) { + return groupMarkId, nil + } + return "", err + } + } +} + +// getLogId 由 generator 服务生成唯一 id +func (s *Service) getLogId(ctx context.Context) (id int64, err error) { + + reply, err := s.idGenRPCClient.GetID() + if err != nil { + return 0, err + } + + return reply, nil +} + +// execGroupCreate 执行创建群操作 +func (s *Service) execGroupCreate(groupInfo *db.GroupInfo, members []*db.GroupMember) error { + tx, err := s.dao.NewTx() + if err != nil { + return err + } + defer tx.RollBack() + if _, _, err = s.dao.InsertGroupInfo(tx, groupInfo); err != nil { + return err + } + + if err = s.InsertGroupMembers(tx, members); err != nil { + return err + } + + if err = tx.Commit(); err != nil { + return err + } + + return nil +} + +func (s *Service) createGroup(ctx context.Context, group *db.GroupInfo, groupMembers []*db.GroupMember) error { + log := s.GetLogWithTrace(ctx) + var err error + groupId := group.GroupId + groupMemberIds := make([]string, 0, len(groupMembers)) + groupOwnerId := "" + for _, groupMember := range groupMembers { + if groupMember.GroupMemberType == biz.GroupMemberTypeOwner { + groupOwnerId = groupMember.GroupMemberId + continue + } + groupMemberIds = append(groupMemberIds, groupMember.GroupMemberId) + } + groupMemberIds = append(groupMemberIds, groupOwnerId) + + if err = s.execGroupCreate(group, groupMembers); err != nil { + return err + } + + go func() { + // 发送给 logic + if err = s.LogicNoticeJoin(contextx.ValueOnlyFrom(ctx), groupId, groupMemberIds); err != nil { + log.Error().Err(err).Msg("createGroup logic") + } + + // 发送给 pusher + if err = s.PusherSignalJoin(contextx.ValueOnlyFrom(ctx), groupId, groupMemberIds); err != nil { + log.Error().Err(err).Msg("createGroup pusher") + } + + if err = s.NoticeMsgSignInGroup(contextx.ValueOnlyFrom(ctx), groupId, s.GetOpe(ctx), groupMemberIds[0:len(groupMemberIds)-1]); err != nil { + log.Error().Err(err).Msg("createGroup alert") + } + }() + + return nil +} + +func (s *Service) CreateGroup(ctx context.Context, group *biz.GroupInfo, owner *biz.GroupMember, members []*biz.GroupMember) (int64, error) { + groupId, err := s.getLogId(ctx) + if err != nil { + return 0, err + } + + groupMarkId, err := s.getRandomGroupMarkId() + if err != nil { + return 0, err + } + + groupMemberNum := int32(1 + len(members)) + if groupMemberNum > biz.GroupMaximum { + return 0, xerror.NewError(xerror.GroupMemberLimit) + } + + groupPo := &db.GroupInfo{ + GroupId: groupId, + GroupMarkId: groupMarkId, + GroupName: group.GroupName, + GroupAvatar: "", + GroupMemberNum: int32(1 + len(members)), + GroupMaximum: biz.GroupMaximum, + GroupIntroduce: "", + GroupStatus: biz.GroupStatusNormal, + GroupOwnerId: owner.GroupMemberId, + GroupJoinType: biz.GroupJoinTypeAny, + GroupMuteType: biz.GroupMuteTypeAny, + GroupFriendType: biz.GroupFriendTypeAllow, + GroupAESKey: xrand.NewAESKey256(), + GroupPubName: group.GroupName, + GroupType: group.GroupType, + } + + if group.GroupType == biz.GroupTypeNormal { + groupPo.GroupJoinType = biz.GroupJoinTypeAny + } else { + groupPo.GroupJoinType = biz.GroupJoinTypeAdmin + } + + ownerPo := &db.GroupMember{ + GroupId: groupId, + GroupMemberId: owner.GroupMemberId, + GroupMemberName: owner.GroupMemberName, + GroupMemberType: biz.GroupMemberTypeOwner, + } + + membersPo := make([]*db.GroupMember, 0, len(members)) + membersPo = append(membersPo, ownerPo) + for _, member := range members { + membersPo = append(membersPo, &db.GroupMember{ + GroupId: groupId, + GroupMemberId: member.GroupMemberId, + GroupMemberName: member.GroupMemberName, + GroupMemberType: biz.GroupMemberTypeNormal, + }) + } + + err = s.createGroup(ctx, groupPo, membersPo) + if err != nil { + return 0, err + } + + return groupId, nil +} diff --git a/service/group/service/creategroupapply.go b/service/group/service/creategroupapply.go new file mode 100644 index 0000000..426157e --- /dev/null +++ b/service/group/service/creategroupapply.go @@ -0,0 +1,76 @@ +package service + +import ( + "context" + + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/service/group/model/biz" + "gitlab.33.cn/chat/dtalk/service/group/model/db" + "gitlab.33.cn/chat/dtalk/service/group/model/types" + "gitlab.33.cn/utils/go-kit/convert" +) + +func (s *Service) CreateGroupApplySvc(ctx context.Context, req *types.CreateGroupApplyReq) (res *types.CreateGroupApplyResp, err error) { + personId := req.PersonId + groupId := convert.ToInt64(req.Id) + + _, err = s.GetGroupInfoByGroupId(ctx, groupId) + if err != nil { + return nil, err + } + + // 判断是否已经在群内 + if isExit, err := s.CheckInGroup(personId, groupId); err != nil { + return nil, err + } else if isExit == true { + return nil, xerror.NewError(xerror.GroupMemberExist) + } + + err = s.ExecCreateGroupApply(groupId, "", []string{personId}, req.ApplyNote) + if err != nil { + return nil, err + } + + return &types.CreateGroupApplyResp{}, nil +} + +func (s *Service) ExecCreateGroupApply(groupId int64, inviterId string, memberIds []string, applyNote string) error { + groupApplys := make([]*db.GroupApply, 0) + nowTime := s.getNowTime() + applyId, err := s.getLogId(context.Background()) + if err != nil { + return err + } + for _, memberId := range memberIds { + groupApply := &db.GroupApply{ + Id: applyId, + GroupId: groupId, + InviterId: inviterId, + MemberId: memberId, + ApplyNote: applyNote, + OperatorId: "", + ApplyStatus: biz.GroupApplyWait, + RejectReason: "", + CreateTime: nowTime, + UpdateTime: nowTime, + } + groupApplys = append(groupApplys, groupApply) + } + + tx, err := s.dao.NewTx() + if err != nil { + return err + } + defer tx.RollBack() + + err = s.dao.InsertGroupApplys(tx, groupApplys) + if err != nil { + return err + } + + if err = tx.Commit(); err != nil { + return err + } + + return nil +} diff --git a/service/group/service/getgroupapplybyid.go b/service/group/service/getgroupapplybyid.go new file mode 100644 index 0000000..c09b569 --- /dev/null +++ b/service/group/service/getgroupapplybyid.go @@ -0,0 +1,37 @@ +package service + +import ( + "context" + + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/service/group/model/biz" + "gitlab.33.cn/chat/dtalk/service/group/model/types" + "gitlab.33.cn/utils/go-kit/convert" +) + +func (s *Service) GetGroupApplyByIdSvc(ctx context.Context, req *types.GetGroupApplyByIdReq) (res *types.GetGroupApplysResp, err error) { + //personId := req.PersonId + applyId := convert.ToInt64(req.ApplyId) + + groupApplyBiz, err := s.getGroupApplyById(applyId) + if err != nil { + return nil, err + } + + return &types.GetGroupApplysResp{ + GroupApplys: []*types.GroupApplyInfo{groupApplyBiz.ToTypes()}, + }, nil +} + +func (s *Service) getGroupApplyById(id int64) (*biz.GroupApplyBiz, error) { + groupApply, err := s.dao.GetGroupApplyById(id) + if err != nil { + return nil, err + } + + if groupApply == nil { + return nil, xerror.NewError(xerror.GroupApplyNotExist) + } + + return groupApply.ToBiz(), nil +} diff --git a/service/group/service/getgroupapplys.go b/service/group/service/getgroupapplys.go new file mode 100644 index 0000000..72d430e --- /dev/null +++ b/service/group/service/getgroupapplys.go @@ -0,0 +1,39 @@ +package service + +import ( + "context" + + "gitlab.33.cn/chat/dtalk/service/group/model/biz" + "gitlab.33.cn/chat/dtalk/service/group/model/types" + "gitlab.33.cn/utils/go-kit/convert" +) + +func (s *Service) GetGroupApplysSvc(ctx context.Context, req *types.GetGroupApplysReq) (res *types.GetGroupApplysResp, err error) { + //personId := req.PersonId + groupId := convert.ToInt64(req.Id) + + groupApplyBizs, err := s.getGroupApplys(groupId, req.Count, req.Offset) + if err != nil { + return nil, err + } + + res = &types.GetGroupApplysResp{} + res.GroupApplys = make([]*types.GroupApplyInfo, 0) + for _, groupApplyBiz := range groupApplyBizs { + res.GroupApplys = append(res.GroupApplys, groupApplyBiz.ToTypes()) + } + + return res, nil +} + +func (s *Service) getGroupApplys(groupId int64, limit, offset int32) ([]*biz.GroupApplyBiz, error) { + groupApplys, err := s.dao.GetGroupApplys(groupId, limit, offset) + if err != nil { + return nil, err + } + groupApplyBizs := make([]*biz.GroupApplyBiz, 0) + for _, groupApply := range groupApplys { + groupApplyBizs = append(groupApplyBizs, groupApply.ToBiz()) + } + return groupApplyBizs, nil +} diff --git a/service/group/service/getgroupinfo.go b/service/group/service/getgroupinfo.go new file mode 100644 index 0000000..a5807a3 --- /dev/null +++ b/service/group/service/getgroupinfo.go @@ -0,0 +1,43 @@ +package service + +import ( + "context" + "gitlab.33.cn/chat/dtalk/service/group/model/types" +) + +// GetGroupInfoHttp 查询群资料 +func (s *Service) GetGroupInfoHttp(ctx context.Context, req *types.GetGroupInfoRequest) (res *types.GetGroupInfoResponse, err error) { + groupId := req.Id + personId := req.PersonId + + group, err := s.GetGroupInfoByGroupId(ctx, groupId) + if err != nil { + return nil, err + } + + personInfo, err := s.GetPersonByMemberIdAndGroupId(ctx, personId, group.GroupId) + if err != nil { + return nil, err + } + + ownerInfo, err := s.GetMemberByMemberIdAndGroupId(ctx, group.GroupOwnerId, group.GroupId) + if err != nil { + return nil, err + } + + memberInfos, err := s.GetGroupMembersByGroupIdWithLimit(group.GroupId, 0, req.DisPlayNum) + if err != nil { + return nil, err + } + + groupType := group.ToTypes(ownerInfo.ToTypes(), personInfo.ToTypes()) + memberInfosType := make([]*types.GroupMember, 0) + for _, member := range memberInfos { + memberInfosType = append(memberInfosType, member.ToTypes()) + } + + res = &types.GetGroupInfoResponse{} + res.GroupInfo = groupType + res.Members = memberInfosType + return res, nil +} diff --git a/service/group/service/getgroupinfobycondition.go b/service/group/service/getgroupinfobycondition.go new file mode 100644 index 0000000..b2922cb --- /dev/null +++ b/service/group/service/getgroupinfobycondition.go @@ -0,0 +1,60 @@ +package service + +import ( + "context" + "errors" + + "gitlab.33.cn/chat/dtalk/service/group/model" + "gitlab.33.cn/chat/dtalk/service/group/model/biz" + "gitlab.33.cn/chat/dtalk/service/group/model/types" + "gitlab.33.cn/utils/go-kit/convert" +) + +// GetGroupInfoByConditionSvc 通过搜索得到群列表 +func (s *Service) GetGroupInfoByConditionSvc(ctx context.Context, req *types.GetGroupInfoByConditionReq) (res *types.GetGroupInfoByConditionResp, err error) { + //personId := req.PersonId + + groups := make([]*biz.GroupInfo, 0) + + switch req.Tp { + case 0: + groupInfo, err := s.dao.GetGroupInfoByGroupMarkId(req.Query) + if err != nil && !errors.Is(err, model.ErrRecordNotExist) { + return nil, err + } + if groupInfo != nil { + if err := groupInfo.IsNormal(); err != nil { + return nil, err + } + groups = append(groups, groupInfo) + } + case 1: + groupId := convert.ToInt64(req.Query) + groupInfo, err := s.dao.GetGroupInfoByGroupId(ctx, groupId) + if err != nil && !errors.Is(err, model.ErrRecordNotExist) { + return nil, err + } + if groupInfo != nil { + if err := groupInfo.IsNormal(); err != nil { + return nil, err + } + groups = append(groups, groupInfo) + } + } + + groupBizInfos := make([]*types.GroupInfo, 0) + for _, group := range groups { + ownerInfo, err := s.GetMemberByMemberIdAndGroupId(ctx, group.GroupOwnerId, group.GroupId) + if err != nil { + return nil, err + } + + groupBizInfo := group.ToTypes(ownerInfo.ToTypes(), nil) + + groupBizInfos = append(groupBizInfos, groupBizInfo) + } + + return &types.GetGroupInfoByConditionResp{ + Groups: groupBizInfos, + }, nil +} diff --git a/service/group/service/getgrouplist.go b/service/group/service/getgrouplist.go new file mode 100644 index 0000000..78e50f0 --- /dev/null +++ b/service/group/service/getgrouplist.go @@ -0,0 +1,44 @@ +package service + +import ( + "context" + + "gitlab.33.cn/chat/dtalk/service/group/model/types" +) + +// TODO check + +// GetGroupListSvc 查询群列表 +func (s *Service) GetGroupListSvc(ctx context.Context, req *types.GetGroupListRequest) (res *types.GetGroupListResponse, err error) { + personId := req.PersonId + + groupIds, err := s.GetGroupIdsByMemberId(personId) + if err != nil { + return nil, err + } + + res = &types.GetGroupListResponse{ + Groups: make([]*types.GroupInfo, len(groupIds), len(groupIds)), + } + + for i, id := range groupIds { + group, err := s.GetGroupInfoByGroupId(ctx, id) + if err != nil { + return nil, err + } + ownerInfo, err := s.GetMemberByMemberIdAndGroupId(ctx, group.GroupOwnerId, group.GroupId) + if err != nil { + return nil, err + } + personInfo, err := s.GetPersonByMemberIdAndGroupId(ctx, personId, group.GroupId) + if err != nil { + return nil, err + } + + groupType := group.ToTypes(ownerInfo.ToTypes(), personInfo.ToTypes()) + + res.Groups[i] = groupType + } + + return res, nil +} diff --git a/service/group/service/getgroupmemberinfo.go b/service/group/service/getgroupmemberinfo.go new file mode 100644 index 0000000..0cf1bc6 --- /dev/null +++ b/service/group/service/getgroupmemberinfo.go @@ -0,0 +1,33 @@ +package service + +import ( + "context" + + "gitlab.33.cn/chat/dtalk/service/group/model/types" +) + +// GetGroupMemberInfoSvc 查询群成员信息 +func (s *Service) GetGroupMemberInfoSvc(ctx context.Context, req *types.GetGroupMemberInfoRequest) (res *types.GetGroupMemberInfoResponse, err error) { + groupId := req.Id + memberId := req.MemberId + personId := req.PersonId + + _, err = s.GetGroupInfoByGroupId(ctx, groupId) + if err != nil { + return nil, err + } + + _, err = s.GetPersonByMemberIdAndGroupId(ctx, personId, groupId) + if err != nil { + return nil, err + } + + member, err := s.GetMemberByMemberIdAndGroupId(ctx, memberId, groupId) + if err != nil { + return nil, err + } + + res = &types.GetGroupMemberInfoResponse{} + res.GroupMember = member.ToTypes() + return res, nil +} diff --git a/service/group/service/getgroupmemberlist.go b/service/group/service/getgroupmemberlist.go new file mode 100644 index 0000000..8f69495 --- /dev/null +++ b/service/group/service/getgroupmemberlist.go @@ -0,0 +1,43 @@ +package service + +import ( + "context" + + "gitlab.33.cn/chat/dtalk/service/group/model/types" + "gitlab.33.cn/utils/go-kit/convert" +) + +// GetGroupMemberListSvc 查询群成员列表 +func (s *Service) GetGroupMemberListSvc(ctx context.Context, req *types.GetGroupMemberListRequest) (res *types.GetGroupMemberListResponse, err error) { + groupId := req.Id + personId := req.PersonId + + _, err = s.GetGroupInfoByGroupId(ctx, groupId) + if err != nil { + return nil, err + } + + _, err = s.GetPersonByMemberIdAndGroupId(ctx, personId, groupId) + if err != nil { + return nil, err + } + + groupMembers, err := s.GetMembersByGroupId(groupId) + if err != nil { + return nil, err + } + + groupMemberTypes := make([]*types.GroupMember, 0) + for _, groupMember := range groupMembers { + groupMemberTypes = append(groupMemberTypes, groupMember.ToTypes()) + } + + res = &types.GetGroupMemberListResponse{ + Id: groupId, + IdStr: convert.ToString(groupId), + Members: groupMemberTypes, + } + + return res, nil + +} diff --git a/service/group/service/getgrouppubinfo.go b/service/group/service/getgrouppubinfo.go new file mode 100644 index 0000000..88d4d39 --- /dev/null +++ b/service/group/service/getgrouppubinfo.go @@ -0,0 +1,43 @@ +package service + +import ( + "context" + + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/service/group/model/types" +) + +// GetGroupPubInfoSvc 查询群资料 +func (s *Service) GetGroupPubInfoSvc(ctx context.Context, req *types.GetGroupPubInfoRequest) (res *types.GetGroupPubInfoResponse, err error) { + groupId := req.Id + personId := req.PersonId + + group, err := s.GetGroupInfoByGroupId(ctx, groupId) + if err != nil { + return nil, err + } + + personInfo, err := s.GetPersonByMemberIdAndGroupId(ctx, personId, group.GroupId) + if err != nil { + if xerror.NewError(xerror.GroupPersonNotExist).Error() != err.Error() { + return nil, err + } + } + + ownerInfo, err := s.GetMemberByMemberIdAndGroupId(ctx, group.GroupOwnerId, group.GroupId) + if err != nil { + return nil, err + } + + res = &types.GetGroupPubInfoResponse{} + var personType *types.GroupMember + if personInfo != nil { + personType = personInfo.ToTypes() + } + res.GroupInfo = group.ToTypes(ownerInfo.ToTypes(), personType) + if personInfo == nil { + res.GroupInfo.AESKey = "" + } + + return res, nil +} diff --git a/service/group/service/getmutelist.go b/service/group/service/getmutelist.go new file mode 100644 index 0000000..cc7a911 --- /dev/null +++ b/service/group/service/getmutelist.go @@ -0,0 +1,48 @@ +package service + +import ( + "context" + "gitlab.33.cn/chat/dtalk/service/group/model/biz" + "gitlab.33.cn/chat/dtalk/service/group/model/types" +) + +// GetMuteListSvc 查询群内禁言列表 +func (s *Service) GetMuteListSvc(ctx context.Context, req *types.GetMuteListRequest) (res *types.GetMuteListResponse, err error) { + groupId := req.Id + personId := req.PersonId + + _, err = s.GetGroupInfoByGroupId(ctx, groupId) + if err != nil { + return nil, err + } + + person, err := s.GetPersonByMemberIdAndGroupId(ctx, personId, groupId) + if err != nil { + return nil, err + } + if err = person.IsAdmin(); err != nil { + return nil, err + } + + muteList, err := s.GetGroupMembersMutedByGroupId(groupId) + if err != nil { + return nil, err + } + + memberTypes := make([]*types.GroupMember, 0) + for _, member := range muteList { + memberTypes = append(memberTypes, member.ToTypes()) + } + + res = &types.GetMuteListResponse{Members: memberTypes} + return res, nil +} + +// GetGroupMembersMutedByGroupId 查询群内被禁言的群成员信息 +func (s *Service) GetGroupMembersMutedByGroupId(groupId int64) ([]*biz.GroupMember, error) { + muteList, err := s.dao.GetGroupMembersMutedByGroupId(groupId) + if err != nil { + return nil, err + } + return muteList, nil +} diff --git a/service/group/service/groupdisband.go b/service/group/service/groupdisband.go new file mode 100644 index 0000000..287d5a7 --- /dev/null +++ b/service/group/service/groupdisband.go @@ -0,0 +1,102 @@ +package service + +import ( + "context" + + "gitlab.33.cn/chat/dtalk/pkg/contextx" + "gitlab.33.cn/chat/dtalk/service/group/model/biz" + "gitlab.33.cn/chat/dtalk/service/group/model/db" + "gitlab.33.cn/chat/dtalk/service/group/model/types" +) + +// GroupDisbandHttp 解散群 +func (s *Service) GroupDisbandHttp(ctx context.Context, req *types.GroupDisbandRequest) (res *types.GroupDisbandResponse, err error) { + groupId := req.Id + personId := req.PersonId + + person, err := s.GetPersonByMemberIdAndGroupId(ctx, personId, groupId) + if err != nil { + return nil, err + } + + // 只有群主可以解散群 + if err = person.IsOwner(); err != nil { + return nil, err + } + + err = s.GroupDisband(ctx, groupId, personId) + if err != nil { + return nil, err + } + + return res, nil +} + +func (s *Service) GroupDisband(ctx context.Context, groupId int64, opeId string) error { + var err error + log := s.GetLogWithTrace(ctx) + + // 执行解散群 + if err = s.ExecGroupDisband(groupId); err != nil { + return err + } + + go func() { + // 发送给 alert + if err = s.NoticeMsgDeleteGroup(contextx.ValueOnlyFrom(ctx), groupId, opeId); err != nil { + log.Error().Err(err).Msg("GroupDisband alert") + } + // 发送给 pusher + if err = s.PusherSignalDel(contextx.ValueOnlyFrom(ctx), groupId); err != nil { + log.Error().Err(err).Msg("GroupDisband pusher") + } + // 发送给 logic + if err = s.LogicNoticeDel(contextx.ValueOnlyFrom(ctx), groupId); err != nil { + log.Error().Err(err).Msg("GroupDisband logic") + } + }() + + return nil +} + +// ExecGroupDisband 执行解散群操作 +func (s *Service) ExecGroupDisband(groupId int64) error { + nowTime := s.getNowTime() + groupMembers, err := s.GetMembersByGroupId(groupId) + if err != nil { + return err + } + + tx, err := s.dao.NewTx() + if err != nil { + return err + } + defer tx.RollBack() + + groupInfo := &db.GroupInfo{ + GroupId: groupId, + GroupStatus: biz.GroupStatusDisBand, + GroupUpdateTime: nowTime, + } + if _, _, err = s.dao.UpdateGroupInfoStatusWithTx(tx, groupInfo); err != nil { + return err + } + + for _, member := range groupMembers { + groupMemberInfo := &db.GroupMember{ + GroupId: groupId, + GroupMemberId: member.GroupMemberId, + GroupMemberUpdateTime: nowTime, + GroupMemberType: biz.GroupMemberTypeOther, + } + if _, _, err = s.dao.UpdateGroupMemberTypeWithTx(tx, groupMemberInfo); err != nil { + return err + } + } + + if err = tx.Commit(); err != nil { + return err + } + + return nil +} diff --git a/service/group/service/groupexit.go b/service/group/service/groupexit.go new file mode 100644 index 0000000..860dca3 --- /dev/null +++ b/service/group/service/groupexit.go @@ -0,0 +1,78 @@ +package service + +import ( + "context" + + "gitlab.33.cn/chat/dtalk/pkg/contextx" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/service/group/model/biz" + "gitlab.33.cn/chat/dtalk/service/group/model/types" +) + +// GroupExitHttp 退群 +func (s *Service) GroupExitHttp(ctx context.Context, req *types.GroupExitRequest) (res *types.GroupExitResponse, err error) { + group, err := s.GetGroupInfoByGroupId(ctx, req.Id) + if err != nil { + return nil, err + } + + //person, err := s.GetPersonByMemberIdAndGroupId(personId, groupId) + person, err := s.GetPersonByMemberIdAndGroupId(ctx, req.PersonId, req.Id) + if err != nil { + return nil, err + } + + if err = person.IsOwner(); err == nil { + return nil, xerror.NewError(xerror.GroupOwnerExit) + } + + err = s.ExitGroup(ctx, group, person) + if err != nil { + return nil, err + } + + return res, nil +} + +func (s *Service) ExitGroup(ctx context.Context, group *biz.GroupInfo, member *biz.GroupMember) error { + groupId := group.GroupId + memberId := member.GroupMemberId + log := s.GetLogWithTrace(ctx) + + // 执行退群 + if err := s.ExecGroupExit(ctx, groupId, memberId); err != nil { + return err + } + + // 更新群人数 + _, err := s.UpdateGroupInfoMemberNum(ctx, groupId) + if err != nil { + log.Error().Err(err).Msg("GroupExitHttp exec") + } + + go func() { + // 发送给 pusher + if err = s.PusherSignalLeave(contextx.ValueOnlyFrom(ctx), groupId, []string{memberId}); err != nil { + log.Error().Err(err).Msg("GroupExitHttp pusher") + } + // 发送给 logic + if err = s.LogicNoticeLeave(contextx.ValueOnlyFrom(ctx), groupId, []string{memberId}); err != nil { + log.Error().Err(err).Msg("GroupExitHttp logic") + } + // 发送给 alert + if err = s.NoticeMsgSignOutGroup(contextx.ValueOnlyFrom(ctx), groupId, memberId); err != nil { + log.Error().Err(err).Msg("GroupExitHttp alert") + } + }() + + return nil +} + +// ExecGroupExit 执行退群操作 +func (s *Service) ExecGroupExit(ctx context.Context, groupId int64, memberId string) error { + err := s.dao.UpdateGroupMemberType(ctx, groupId, memberId, biz.GroupMemberTypeOther) + if err != nil { + return err + } + return nil +} diff --git a/service/group/service/groupremove.go b/service/group/service/groupremove.go new file mode 100644 index 0000000..a6db3ca --- /dev/null +++ b/service/group/service/groupremove.go @@ -0,0 +1,118 @@ +package service + +import ( + "context" + "gitlab.33.cn/chat/dtalk/pkg/contextx" + "gitlab.33.cn/chat/dtalk/service/group/model/biz" + "gitlab.33.cn/chat/dtalk/service/group/model/db" + "gitlab.33.cn/chat/dtalk/service/group/model/types" +) + +// GroupRemoveSvc 踢人 +func (s *Service) GroupRemoveSvc(ctx context.Context, req *types.GroupRemoveRequest) (res *types.GroupRemoveResponse, err error) { + groupId := req.Id + memberIds := req.MemberIds + personId := req.PersonId + + // 判断一下该群是否存在 + group, err := s.GetGroupInfoByGroupId(ctx, groupId) + if err != nil { + return nil, err + } + + // 判断踢人者权限 + person, err := s.GetPersonByMemberIdAndGroupId(ctx, personId, groupId) + if err != nil { + return nil, err + } + if err = person.IsAdmin(); err != nil { + return nil, err + } + + // 过滤可以踢人的列表 + needDeleteMembers := s.GetFilteredGroupMembers(ctx, group, memberIds) + if len(needDeleteMembers) == 0 { + return &types.GroupRemoveResponse{MemberNum: group.GroupMemberNum}, nil + } + + canRemoveMemberIds := make([]string, 0) + canRemoveMembers := make([]*biz.GroupMember, 0) + for _, member := range needDeleteMembers { + if err := person.RemoveOneMember(member); err != nil { + return nil, err + } + + canRemoveMemberIds = append(canRemoveMemberIds, member.GroupMemberId) + canRemoveMembers = append(canRemoveMembers, member) + } + + // 执行踢人 + if err = s.RemoveGroupMembers(ctx, group, canRemoveMembers); err != nil { + return nil, err + } + + res = &types.GroupRemoveResponse{ + MemberIds: canRemoveMemberIds, + } + return res, nil +} + +// RemoveGroupMembers 执行踢人操作 +func (s *Service) RemoveGroupMembers(ctx context.Context, group *biz.GroupInfo, members []*biz.GroupMember) error { + if len(members) == 0 { + return nil + } + groupId := group.GroupId + memberIds := make([]string, 0, len(members)) + for _, member := range members { + memberIds = append(memberIds, member.GroupMemberId) + } + + log := s.GetLogWithTrace(ctx) + nowTime := s.getNowTime() + + tx, err := s.dao.NewTx() + if err != nil { + return err + } + defer tx.RollBack() + + for _, memberId := range memberIds { + groupMemberInfo := &db.GroupMember{ + GroupId: groupId, + GroupMemberId: memberId, + GroupMemberUpdateTime: nowTime, + GroupMemberType: biz.GroupMemberTypeOther, + } + if _, _, err = s.dao.UpdateGroupMemberTypeWithTx(tx, groupMemberInfo); err != nil { + return err + } + } + + if err = tx.Commit(); err != nil { + return err + } + + // 更新群人数 + _, err = s.UpdateGroupInfoMemberNum(ctx, groupId) + if err != nil { + log.Error().Err(err).Msg("GroupRemoveSvc") + } + + go func() { + // 发送给 alert + if err = s.NoticeMsgKickOutGroup(contextx.ValueOnlyFrom(ctx), groupId, s.GetOpe(ctx), memberIds); err != nil { + log.Error().Err(err).Msg("GroupRemoveSvc alert") + } + // 发送个 pusher + if err = s.PusherSignalLeave(contextx.ValueOnlyFrom(ctx), groupId, memberIds); err != nil { + log.Error().Msg("GroupRemoveSvc pusher") + } + // 发送给 logic + if err = s.LogicNoticeLeave(contextx.ValueOnlyFrom(ctx), groupId, memberIds); err != nil { + log.Error().Msg("GroupRemoveSvc logic") + } + }() + + return nil +} diff --git a/service/group/service/invitegroupmembers.go b/service/group/service/invitegroupmembers.go new file mode 100644 index 0000000..37c042b --- /dev/null +++ b/service/group/service/invitegroupmembers.go @@ -0,0 +1,181 @@ +package service + +import ( + "context" + + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/service/group/model/biz" + "gitlab.33.cn/chat/dtalk/service/group/model/db" + "gitlab.33.cn/chat/dtalk/service/group/model/types" + "gitlab.33.cn/utils/go-kit/convert" +) + +// InviteGroupMembersSvc 邀请新群员 +// 加群设置为 Any +// - 邀请人为空, 直接加入 +// - 邀请人不为空, 直接加入 +// 加群设置为 Apply +// - 邀请人为空, 走审批流程 +// - 邀请人为普通人, 走审批流程 +// - 邀请人为管理员, 直接加入 +// 加群设置为 Admin +// - 邀请人为空, 拒绝加入 +// - 邀请人为普通人, 拒绝加入 +// - 邀请人为管理员, 直接加入 +func (s *Service) InviteGroupMembersSvc(ctx context.Context, req *types.InviteGroupMembersRequest) (res *types.InviteGroupMembersResponse, err error) { + //personId := req.Inviter.MemberId + groupId := req.Id + + group, err := s.GetGroupInfoByGroupId(ctx, req.Id) + if err != nil { + return nil, err + } + + if req.Inviter.MemberId == "" { + if group.GroupJoinType == biz.GroupJoinTypeAny { + return s.inviteMember(ctx, group, req) + } else if group.GroupJoinType == biz.GroupJoinTypeApply { + return s.invite2apply(req.NewMemberIds, groupId, "") + } + return nil, xerror.NewError(xerror.GroupInvitePermissionDenied) + } + + // 得到邀请人信息 + inviter, err := s.GetPersonByMemberIdAndGroupId(ctx, req.Inviter.MemberId, groupId) + if err != nil { + return nil, err + } + + switch inviter.TryInvite(group) { + case biz.InviteOk: + return s.inviteMember(ctx, group, req) + case biz.InviteApply: + return s.invite2apply(req.NewMemberIds, groupId, inviter.GroupMemberId) + case biz.InviteFail: + return nil, xerror.NewError(xerror.GroupInvitePermissionDenied) + } + return nil, xerror.NewError(xerror.CodeInnerError) +} + +// ExecInviteGroupMembers 执行邀请群成员操作 +//func (s *Service) ExecInviteGroupMembers(members []*db.GroupMember) error { +// tx, err := s.dao.NewTx() +// if err != nil { +// return err +// } +// +// defer tx.RollBack() +// +// if err = s.InsertGroupMembers(tx, members); err != nil { +// return err +// } +// +// if err = tx.Commit(); err != nil { +// return err +// } +// +// return nil +//} + +func (s *Service) invite2apply(MemberIds []string, groupId int64, inviterId string) (res *types.InviteGroupMembersResponse, err error) { + newMemberIds := []string{} + for _, memberId := range MemberIds { + // 判断是否已经在群内 + if isExit, err := s.CheckInGroup(memberId, groupId); err != nil { + continue + } else if isExit == true { + continue + } + + newMemberIds = append(newMemberIds, memberId) + } + + if len(newMemberIds) == 0 { + return nil, xerror.NewError(xerror.GroupInviteNoMembers) + } + + err = s.ExecCreateGroupApply(groupId, inviterId, newMemberIds, "") + if err != nil { + return nil, err + } + + return &types.InviteGroupMembersResponse{}, nil +} + +func (s *Service) inviteMember(ctx context.Context, group *biz.GroupInfo, req *types.InviteGroupMembersRequest) (res *types.InviteGroupMembersResponse, err error) { + groupId := group.GroupId + personId := req.Inviter.MemberId + + // 判断群人数上限 + if err = group.TryJoin(int32(len(req.NewMembers))); err != nil { + return nil, err + } + + // 插入新群员 + nowTime := s.getNowTime() + newMembers := make([]*db.GroupMember, 0) + for _, member := range req.NewMembers { + // 判断是否已经在群内 + if isExit, err := s.CheckInGroup(member.MemberId, groupId); err != nil { + continue + } else if isExit == true { + continue + } + + newMembers = append(newMembers, &db.GroupMember{ + GroupId: groupId, + GroupMemberId: member.MemberId, + GroupMemberName: member.MemberName, + GroupMemberType: member.MemberType, + GroupMemberJoinTime: nowTime, + GroupMemberUpdateTime: nowTime, + }) + } + + // 被邀请的人都已经在本群中 + if len(newMembers) == 0 { + err = xerror.NewError(xerror.GroupInviteNoMembers) + return nil, err + } + + if err = s.AddGroupMembers(ctx, group.GroupId, newMembers, personId); err != nil { + return nil, err + } + + // ? 不知道有没有用 + res = &types.InviteGroupMembersResponse{ + Id: req.Id, + IdStr: convert.ToString(groupId), + MemberNum: group.GroupMemberNum + int32(len(newMembers)), + //Inviter: req.Inviter, + //NewMembers: model.GroupMemberConvertGroupMemberInfo(newMembers), + } + return res, nil +} + +func (s *Service) InviteMembers(ctx context.Context, group *biz.GroupInfo, newMemberIds []string) error { + groupId := group.GroupId + + // 判断群人数上限 + if err := group.TryJoin(int32(len(newMemberIds))); err != nil { + return err + } + + // 插入新群员 + members := make([]*biz.GroupMember, 0, len(newMemberIds)) + for _, memberId := range newMemberIds { + members = append(members, &biz.GroupMember{ + GroupId: groupId, + GroupMemberId: memberId, + GroupMemberName: "", + GroupMemberType: biz.GroupTypeNormal, + }) + } + + err := s.AddMembers(ctx, group, members) + if err != nil { + return err + } + + return nil +} diff --git a/service/group/service/joingroup.go b/service/group/service/joingroup.go new file mode 100644 index 0000000..9008918 --- /dev/null +++ b/service/group/service/joingroup.go @@ -0,0 +1,154 @@ +package service + +import ( + "context" + "gitlab.33.cn/chat/dtalk/service/group/model/biz" + "gitlab.33.cn/chat/dtalk/service/group/model/db" +) + +// JoinGroupSvc 加入群聊, grpc 专属 +//func (s *Service) JoinGroupSvc(ctx context.Context, req *types.JoinGroupReq) (res *types.JoinGroupResp, err error) { +// personId := req.PersonId +// groupId := req.Id +// +// group, err := s.GetGroupInfoByGroupId(ctx, groupId) +// if err != nil { +// return nil, err +// } +// +// // 判断群人数上限 +// if err = group.TryJoin(1); err != nil { +// return nil, err +// } +// +// // 判断是否已经在群内 +// if isExit, err := s.CheckInGroup(personId, groupId); err != nil { +// return nil, err +// } else if isExit == true { +// return nil, xerror.NewError(xerror.GroupInviteMemberExist) +// } +// +// nowTime := s.getNowTime() +// newMember := &db.GroupMember{ +// GroupId: groupId, +// GroupMemberId: personId, +// GroupMemberName: "", +// GroupMemberType: 0, +// GroupMemberJoinTime: nowTime, +// GroupMemberUpdateTime: nowTime, +// } +// +// if err = s.AddGroupMembers(ctx, group.GroupId, []*db.GroupMember{newMember}, group.GroupOwnerId); err != nil { +// return nil, err +// } +// +// res = &types.JoinGroupResp{ +// Id: groupId, +// IdStr: convert.ToString(groupId), +// } +// +// return +//} + +// ExecJoinGroupMembers 执行邀请群成员操作 +func (s *Service) ExecJoinGroupMembers(members []*db.GroupMember) error { + tx, err := s.dao.NewTx() + if err != nil { + return err + } + + defer tx.RollBack() + + if err = s.InsertGroupMembers(tx, members); err != nil { + return err + } + + if err = tx.Commit(); err != nil { + return err + } + + return nil +} + +func (s *Service) GetFilteredGroupMembers(ctx context.Context, group *biz.GroupInfo, memberIds []string) []*biz.GroupMember { + members := make([]*biz.GroupMember, 0, len(memberIds)) + for _, memberId := range memberIds { + member, err := s.GetMemberByMemberIdAndGroupId(ctx, memberId, group.GroupId) + if err != nil { + continue + } + + members = append(members, member) + } + + return members +} + +// FilteredGroupMembers 过滤已经在群里的成员 +func (s *Service) FilteredGroupMembers(members []*biz.GroupMember) []*biz.GroupMember { + newMembers := make([]*biz.GroupMember, 0) + for _, member := range members { + // 判断是否已经在群内 + if isExit, err := s.CheckInGroup(member.GroupMemberId, member.GroupId); err != nil { + continue + } else if isExit == true { + continue + } + + newMembers = append(newMembers, member) + } + + return newMembers +} + +func (s *Service) AddMembers(ctx context.Context, group *biz.GroupInfo, members []*biz.GroupMember) error { + members = s.FilteredGroupMembers(members) + + if len(members) == 0 { + return nil + } + + nowTime := s.getNowTime() + newMembers := make([]*db.GroupMember, 0, len(members)) + for _, member := range members { + newMembers = append(newMembers, &db.GroupMember{ + GroupId: member.GroupId, + GroupMemberId: member.GroupMemberId, + GroupMemberName: member.GroupMemberName, + GroupMemberType: member.GroupMemberType, + GroupMemberJoinTime: nowTime, + GroupMemberUpdateTime: nowTime, + }) + } + + if err := s.AddGroupMembers(ctx, group.GroupId, newMembers, s.GetOpe(ctx)); err != nil { + return err + } + + return nil +} + +func (s *Service) JoinGroups(ctx context.Context, members []*biz.GroupMember) { + log := s.GetLogWithTrace(ctx) + members = s.FilteredGroupMembers(members) + + if len(members) == 0 { + return + } + + nowTime := s.getNowTime() + for _, member := range members { + newMember := &db.GroupMember{ + GroupId: member.GroupId, + GroupMemberId: member.GroupMemberId, + GroupMemberName: member.GroupMemberName, + GroupMemberType: member.GroupMemberType, + GroupMemberJoinTime: nowTime, + GroupMemberUpdateTime: nowTime, + } + + if err := s.AddGroupMembers(ctx, member.GroupId, []*db.GroupMember{newMember}, s.GetOpe(ctx)); err != nil { + log.Error().Err(err).Msg("JoinGroups") + } + } +} diff --git a/service/group/service/maintain.go b/service/group/service/maintain.go new file mode 100644 index 0000000..56ddc96 --- /dev/null +++ b/service/group/service/maintain.go @@ -0,0 +1,33 @@ +package service + +import ( + "gitlab.33.cn/chat/dtalk/pkg/rand" + "gitlab.33.cn/chat/dtalk/service/group/model/db" +) + +// MaintainGroupAESKey 给旧的群设置 aes key +func (s *Service) MaintainGroupAESKey() error { + // 得到group列表 + groups, err := s.dao.GetAllGroupInfo() + if err != nil { + s.log.Error().Err(err).Msg("GetAllGroupInfo") + return err + } + + for _, group := range groups { + if group.AESKey == "" { + groupPo := &db.GroupInfo{ + GroupId: group.GroupId, + GroupName: group.GroupName, + GroupAESKey: rand.NewAESKey256(), + GroupPubName: group.GroupName, + } + _, _, err := s.dao.MaintainAESKeyAndPubName(groupPo) + if err != nil { + s.log.Error().Err(err).Interface("groupPo", groupPo).Msg("MaintainAESKeyAndPubName") + } + } + } + + return nil +} diff --git a/service/group/service/rejectgroupapply.go b/service/group/service/rejectgroupapply.go new file mode 100644 index 0000000..447f260 --- /dev/null +++ b/service/group/service/rejectgroupapply.go @@ -0,0 +1,45 @@ +package service + +import ( + "context" + + "gitlab.33.cn/chat/dtalk/service/group/model/biz" + "gitlab.33.cn/chat/dtalk/service/group/model/types" + "gitlab.33.cn/utils/go-kit/convert" +) + +func (s *Service) RejectGroupApplySvc(ctx context.Context, req *types.RejectGroupApplyReq) (res *types.RejectGroupApplyResp, err error) { + personId := req.PersonId + groupId := convert.ToInt64(req.Id) + applyId := convert.ToInt64(req.ApplyId) + + // 查询审批详情 + groupApply, err := s.getGroupApplyById(applyId) + if err != nil { + return nil, err + } + + // 判断审批是否被处理过 + if err = groupApply.IsWait(); err != nil { + return nil, err + } + + // 查询操作人详情并判断是否有权限操作 + person, err := s.GetPersonByMemberIdAndGroupId(ctx, personId, groupId) + if err != nil { + return nil, err + } + if err = person.IsAdmin(); err != nil { + return nil, err + } + + // 处理审批 + groupApply.ApplyStatus = biz.GroupApplyReject + groupApply.OperatorId = personId + err = s.updateGroupApply(groupApply) + if err != nil { + return nil, err + } + + return &types.RejectGroupApplyResp{}, nil +} diff --git a/service/group/service/service.go b/service/group/service/service.go new file mode 100644 index 0000000..72efb88 --- /dev/null +++ b/service/group/service/service.go @@ -0,0 +1,105 @@ +package service + +import ( + "context" + "fmt" + "gitlab.33.cn/chat/dtalk/pkg/api" + "time" + + "gitlab.33.cn/chat/dtalk/pkg/api/trace" + "gitlab.33.cn/chat/dtalk/pkg/logger" + "gitlab.33.cn/chat/dtalk/service/group/model/biz" + + "github.com/rs/zerolog" + "gitlab.33.cn/chat/dtalk/pkg/naming" + "gitlab.33.cn/chat/dtalk/pkg/net/grpc" + idgen "gitlab.33.cn/chat/dtalk/service/generator/api" + "gitlab.33.cn/chat/dtalk/service/group/config" + "gitlab.33.cn/chat/dtalk/service/group/dao" + answer "gitlab.33.cn/chat/dtalk/service/record/answer/api" + logic "gitlab.33.cn/chat/im/api/logic/grpc" + "google.golang.org/grpc/resolver" +) + +type Service struct { + log zerolog.Logger + cfg *config.Config + dao *dao.Dao + idGenRPCClient *idgen.Client + logicClient logic.LogicClient + answerClient *answer.Client +} + +var srvName = "group/srv" + +func New(cfg *config.Config) *Service { + s := &Service{ + log: logger.New(cfg.Env, srvName), + cfg: cfg, + dao: dao.New(cfg), + idGenRPCClient: idgen.New(cfg.IdGenRPCClient.RegAddrs, cfg.IdGenRPCClient.Schema, cfg.IdGenRPCClient.SrvName, time.Duration(cfg.IdGenRPCClient.Dial)), + logicClient: newLogicClient(cfg), + answerClient: answer.New(cfg.AnswerRPCClient.RegAddrs, cfg.AnswerRPCClient.Schema, cfg.AnswerRPCClient.SrvName, time.Duration(cfg.AnswerRPCClient.Dial)), + } + + initGroupDefault(cfg.GroupInfoConfig) + + return s +} + +func newLogicClient(cfg *config.Config) logic.LogicClient { + rb := naming.NewResolver(cfg.LogicRPCClient.RegAddrs, cfg.LogicRPCClient.Schema) + resolver.Register(rb) + + addr := fmt.Sprintf("%s:///%s", cfg.LogicRPCClient.Schema, cfg.LogicRPCClient.SrvName) // "schema://[authority]/service" + fmt.Println("logic rpc client call addr:", addr) + + conn, err := grpc.NewGRPCConn(addr, time.Duration(cfg.LogicRPCClient.Dial)) + if err != nil { + panic(err) + } + return logic.NewLogicClient(conn) +} + +func (s *Service) Ping() error { + return nil +} + +func (s Service) Config() *config.Config { + return s.cfg +} + +func initGroupDefault(cfg *config.GroupDefault) { + if cfg.GroupMaximum < 200 { + cfg.GroupMaximum = 200 + } + if cfg.GroupMaximum > 2000 { + cfg.GroupMaximum = 2000 + } + biz.GroupMaximum = cfg.GroupMaximum + + if cfg.AdminNum < 10 { + cfg.AdminNum = 10 + } + if cfg.AdminNum > 10 { + cfg.AdminNum = 10 + } + biz.AdminNum = cfg.AdminNum +} + +func (s *Service) GetLog() zerolog.Logger { + return s.log +} + +func (s *Service) GetLogWithTrace(ctx context.Context) zerolog.Logger { + logId := s.GetTrace(ctx) + return s.log.With().Str("trace", logId).Logger() +} + +func (s *Service) GetTrace(ctx context.Context) string { + return trace.NewTraceIdWithContext(ctx) +} + +func (s *Service) GetOpe(ctx context.Context) string { + return api.NewAddrWithContext(ctx) +} diff --git a/service/group/service/setadmin.go b/service/group/service/setadmin.go new file mode 100644 index 0000000..7f5ed9f --- /dev/null +++ b/service/group/service/setadmin.go @@ -0,0 +1,134 @@ +package service + +import ( + "context" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/service/group/model/biz" + "gitlab.33.cn/chat/dtalk/service/group/model/db" + "gitlab.33.cn/chat/dtalk/service/group/model/types" +) + +// SetAdminSvc 设置管理员 +func (s *Service) SetAdminSvc(ctx context.Context, req *types.SetAdminRequest) (res *types.SetAdminResponse, err error) { + groupId := req.Id + personId := req.PersonId + memberId := req.MemberId + memberType := req.MemberType + log := s.GetLogWithTrace(ctx) + + group, err := s.GetGroupInfoByGroupId(ctx, groupId) + if err != nil { + return nil, err + } + + person, err := s.GetPersonByMemberIdAndGroupId(ctx, personId, groupId) + if err != nil { + return nil, err + } + // 只有群主可以设置管理员 + if personId != group.GroupOwnerId || person.GroupMemberType != biz.GroupMemberTypeOwner || memberId == personId { + err = xerror.NewError(xerror.GroupOwnerSetAdmin) + return nil, err + } + + _, err = s.GetMemberByMemberIdAndGroupId(ctx, memberId, groupId) + if err != nil { + return nil, err + } + + memberType, err = s.CheckSetAdminType(memberType) + if err != nil { + return nil, err + } + + if err = group.TrySetAdmin(); memberType == biz.GroupMemberTypeAdmin && err != nil { + return nil, err + } + + if err = s.UpdateGroupMemberType(ctx, groupId, memberId, memberType); err != nil { + return nil, err + } + + // 发送给 pusher + if err = s.PusherSignalMemberType(ctx, groupId, memberId, memberType); err != nil { + log.Error().Err(err).Msg("SetAdminSvc pusher") + } + + if memberType == biz.GroupMuteTypeAdmin { + // 解除新管理员禁言 + nowTime := s.getNowTime() + groupMemberMute := make([]*db.GroupMemberMute, 1, 1) + groupMemberMute[0] = &db.GroupMemberMute{ + GroupId: groupId, + GroupMemberId: memberId, + GroupMemberMuteTime: 0, + GroupMemberMuteUpdateTime: nowTime, + } + if err = s.execSetMemberMuteTimes(ctx, groupMemberMute); err != nil { + return nil, err + } + + // 发送给 pusher + if err = s.PusherSignalMemberMuteTime(ctx, groupId, []string{memberId}, 0); err != nil { + log.Error().Err(err).Msg("UpdateMembersMuteTimeSvc pusher") + } + } + + return res, nil +} + +// CheckSetAdminType 检查SetAdmin是否合法 +func (s *Service) CheckSetAdminType(memberType int32) (int32, error) { + switch memberType { + case biz.GroupMemberTypeNormal: + return biz.GroupMemberTypeNormal, nil + case biz.GroupMuteTypeAdmin: + return biz.GroupMemberTypeAdmin, nil + default: + return 0, xerror.NewError(xerror.ParamsError) + } +} + +// UpdateGroupMemberType 更新群成员类型 +func (s *Service) UpdateGroupMemberType(ctx context.Context, groupId int64, memberId string, memberType int32) error { + err := s.dao.UpdateGroupMemberType(ctx, groupId, memberId, memberType) + if err != nil { + return err + } + return nil +} + +func (s *Service) SetAdmin(ctx context.Context, group *biz.GroupInfo, member *biz.GroupMember, memberType int32) error { + log := s.GetLogWithTrace(ctx) + groupId := group.GroupId + memberId := member.GroupMemberId + + if err := s.UpdateGroupMemberType(ctx, groupId, memberId, memberType); err != nil { + return err + } + + // 发送给 pusher + if err := s.PusherSignalMemberType(ctx, groupId, memberId, memberType); err != nil { + log.Error().Err(err).Msg("SetAdmin pusher") + } + + if memberType == biz.GroupMuteTypeAdmin { + // 解除新管理员禁言 + groupMemberMute := make([]*db.GroupMemberMute, 1, 1) + groupMemberMute[0] = &db.GroupMemberMute{ + GroupId: groupId, + GroupMemberId: memberId, + GroupMemberMuteTime: 0, + } + if err := s.execSetMemberMuteTimes(ctx, groupMemberMute); err != nil { + log.Error().Err(err).Msg("execSetMemberMuteTimes pusher") + } + + // 发送给 pusher + if err := s.PusherSignalMemberMuteTime(ctx, groupId, []string{memberId}, 0); err != nil { + log.Error().Err(err).Msg("PusherSignalMemberMuteTime pusher") + } + } + + return nil +} diff --git a/service/group/service/updategroupavatar.go b/service/group/service/updategroupavatar.go new file mode 100644 index 0000000..74eb7bb --- /dev/null +++ b/service/group/service/updategroupavatar.go @@ -0,0 +1,59 @@ +package service + +import ( + "context" + "gitlab.33.cn/chat/dtalk/service/group/model/biz" + + "gitlab.33.cn/chat/dtalk/service/group/model/types" +) + +// UpdateGroupAvatarSvc 更新群头像 +func (s *Service) UpdateGroupAvatarSvc(ctx context.Context, req *types.UpdateGroupAvatarRequest) (res *types.UpdateGroupAvatarResponse, err error) { + groupId := req.Id + personId := req.PersonId + groupAvatar := req.Avatar + + group, err := s.GetGroupInfoByGroupId(ctx, groupId) + if err != nil { + return nil, err + } + + person, err := s.GetPersonByMemberIdAndGroupId(ctx, personId, groupId) + if err != nil { + return nil, err + } + if err = person.IsAdmin(); err != nil { + return nil, err + } + + if err := s.UpdateGroupAvatar(ctx, group, groupAvatar); err != nil { + return nil, err + } + + return res, nil +} + +// updateGroupAvatar 更新db群头像 +func (s *Service) updateGroupAvatar(ctx context.Context, groupId int64, avatar string) error { + err := s.dao.UpdateGroupInfoAvatar(ctx, groupId, avatar) + if err != nil { + return err + } + return nil +} + +func (s *Service) UpdateGroupAvatar(ctx context.Context, group *biz.GroupInfo, avatar string) error { + log := s.GetLogWithTrace(ctx) + groupId := group.GroupId + + if err := s.updateGroupAvatar(ctx, groupId, avatar); err != nil { + return err + } + + // 发送给 pusher + if err := s.PusherSignalGroupAvatar(ctx, groupId, avatar); err != nil { + log.Error().Err(err).Msg("UpdateGroupAvatar pusher") + } + + return nil +} diff --git a/service/group/service/updategroupfriendtype.go b/service/group/service/updategroupfriendtype.go new file mode 100644 index 0000000..ed58b31 --- /dev/null +++ b/service/group/service/updategroupfriendtype.go @@ -0,0 +1,77 @@ +package service + +import ( + "context" + + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/service/group/model/biz" + "gitlab.33.cn/chat/dtalk/service/group/model/types" +) + +// UpdateGroupFriendTypeSvc 更新群内加好友设置 +func (s *Service) UpdateGroupFriendTypeSvc(ctx context.Context, req *types.UpdateGroupFriendTypeRequest) (res *types.UpdateGroupFriendTypeResponse, err error) { + groupId := req.Id + personId := req.PersonId + friendType := req.FriendType + + group, err := s.GetGroupInfoByGroupId(ctx, groupId) + if err != nil { + return nil, err + } + + person, err := s.GetPersonByMemberIdAndGroupId(ctx, personId, groupId) + if err != nil { + return nil, err + } + if err = person.IsAdmin(); err != nil { + return nil, err + } + + if err := s.UpdateGroupFriendType(ctx, group, friendType); err != nil { + return nil, err + } + + return res, nil +} + +// CheckGroupFriendType 检查friendType是否合法 +func (s *Service) CheckGroupFriendType(friendType int32) (int32, error) { + switch friendType { + case biz.GroupFriendTypeAllow: + return biz.GroupFriendTypeAllow, nil + case biz.GroupFriendTypeDeny: + return biz.GroupFriendTypeDeny, nil + default: + return 0, xerror.NewError(xerror.ParamsError) + } +} + +// updateGroupFriendType 更新群内加好友设置 +func (s *Service) updateGroupFriendType(ctx context.Context, groupId int64, friendType int32) error { + err := s.dao.UpdateGroupInfoFriendType(ctx, groupId, friendType) + if err != nil { + return err + } + return nil +} + +func (s *Service) UpdateGroupFriendType(ctx context.Context, group *biz.GroupInfo, friendType int32) error { + log := s.GetLogWithTrace(ctx) + groupId := group.GroupId + + friendType, err := s.CheckGroupFriendType(friendType) + if err != nil { + return err + } + + if err = s.updateGroupFriendType(ctx, groupId, friendType); err != nil { + return err + } + + // 发送给 pusher + if err = s.PusherSignalFriendType(ctx, groupId, friendType); err != nil { + log.Error().Err(err).Msg("UpdateGroupFriendTypeSvc pusher") + } + + return nil +} diff --git a/service/group/service/updategroupjointype.go b/service/group/service/updategroupjointype.go new file mode 100644 index 0000000..4ede514 --- /dev/null +++ b/service/group/service/updategroupjointype.go @@ -0,0 +1,80 @@ +package service + +import ( + "context" + + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/service/group/model/biz" + "gitlab.33.cn/chat/dtalk/service/group/model/types" +) + +// UpdateGroupJoinTypeSvc 更新加群设置 +// todo 改造完后就弃用 +func (s *Service) UpdateGroupJoinTypeSvc(ctx context.Context, req *types.UpdateGroupJoinTypeRequest) (res *types.UpdateGroupJoinTypeResponse, err error) { + groupId := req.Id + personId := req.PersonId + joinType := req.JoinType + + group, err := s.GetGroupInfoByGroupId(ctx, groupId) + if err != nil { + return nil, err + } + + person, err := s.GetPersonByMemberIdAndGroupId(ctx, personId, groupId) + if err != nil { + return nil, err + } + if err = person.IsAdmin(); err != nil { + return nil, err + } + + if err := s.UpdateGroupJoinType(ctx, group, joinType); err != nil { + return nil, err + } + + return res, nil +} + +// CheckGroupJoinType 检查joinType是否合法 +func (s *Service) CheckGroupJoinType(joinType int32) (int32, error) { + switch joinType { + case biz.GroupJoinTypeAny: + return biz.GroupJoinTypeAny, nil + case biz.GroupJoinTypeAdmin: + return biz.GroupJoinTypeAdmin, nil + case biz.GroupJoinTypeApply: + return biz.GroupJoinTypeApply, nil + default: + return 0, xerror.NewError(xerror.ParamsError) + } +} + +// updateGroupJoinType 更新加群设置 +func (s *Service) updateGroupJoinType(ctx context.Context, groupId int64, joinType int32) error { + err := s.dao.UpdateGroupInfoJoinType(ctx, groupId, joinType) + if err != nil { + return err + } + return nil +} + +func (s *Service) UpdateGroupJoinType(ctx context.Context, group *biz.GroupInfo, joinType int32) error { + log := s.GetLogWithTrace(ctx) + groupId := group.GroupId + + joinType, err := s.CheckGroupJoinType(joinType) + if err != nil { + return err + } + + if err = s.updateGroupJoinType(ctx, groupId, joinType); err != nil { + return err + } + + // 发送给 pusher + if err = s.PusherSignalJoinType(ctx, groupId, joinType); err != nil { + log.Error().Err(err).Msg("UpdateGroupJoinType pusher") + } + + return nil +} diff --git a/service/group/service/updategroupmutetype.go b/service/group/service/updategroupmutetype.go new file mode 100644 index 0000000..b8d17da --- /dev/null +++ b/service/group/service/updategroupmutetype.go @@ -0,0 +1,82 @@ +package service + +import ( + "context" + + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/service/group/model/biz" + "gitlab.33.cn/chat/dtalk/service/group/model/types" +) + +// UpdateGroupMuteTypeSvc 更新群禁言设置 +func (s *Service) UpdateGroupMuteTypeSvc(ctx context.Context, req *types.UpdateGroupMuteTypeRequest) (res *types.UpdateGroupMuteTypeResponse, err error) { + groupId := req.Id + personId := req.PersonId + muteType := req.MuteType + + group, err := s.GetGroupInfoByGroupId(ctx, groupId) + if err != nil { + return nil, err + } + + person, err := s.GetPersonByMemberIdAndGroupId(ctx, personId, groupId) + if err != nil { + return nil, err + } + if err = person.IsAdmin(); err != nil { + return nil, err + } + + if err := s.UpdateGroupMuteType(ctx, group, muteType); err != nil { + return nil, err + } + + return res, nil +} + +// CheckGroupMuteType 检查muteType是否合法 +func (s *Service) CheckGroupMuteType(muteType int32) (int32, error) { + switch muteType { + case biz.GroupMuteTypeAny: + return biz.GroupMuteTypeAny, nil + case biz.GroupMuteTypeAdmin: + return biz.GroupMuteTypeAdmin, nil + default: + return 0, xerror.NewError(xerror.ParamsError) + } +} + +// updateGroupMuteType 更新群禁言设置 +func (s *Service) updateGroupMuteType(ctx context.Context, groupId int64, muteType int32) error { + err := s.dao.UpdateGroupInfoMuteType(ctx, groupId, muteType) + if err != nil { + return err + } + return nil +} + +func (s *Service) UpdateGroupMuteType(ctx context.Context, group *biz.GroupInfo, muteType int32) error { + log := s.GetLogWithTrace(ctx) + groupId := group.GroupId + + muteType, err := s.CheckGroupMuteType(muteType) + if err != nil { + return err + } + + if err = s.updateGroupMuteType(ctx, groupId, muteType); err != nil { + return err + } + + // 发送给 pusher + if err = s.PusherSignalMuteType(ctx, groupId, muteType); err != nil { + log.Error().Err(err).Msg("UpdateGroupMuteType pusher") + } + + // 发送给 alert + if err = s.NoticeMsgUpdateGroupMuted(ctx, groupId, s.GetOpe(ctx), muteType); err != nil { + log.Error().Err(err).Msg("UpdateGroupMuteType alert") + } + + return nil +} diff --git a/service/group/service/updategroupname.go b/service/group/service/updategroupname.go new file mode 100644 index 0000000..060a030 --- /dev/null +++ b/service/group/service/updategroupname.go @@ -0,0 +1,76 @@ +package service + +import ( + "context" + "gitlab.33.cn/chat/dtalk/service/group/model/biz" + + "gitlab.33.cn/chat/dtalk/service/group/model/types" +) + +// UpdateGroupNameSvc 更新群名称 +func (s *Service) UpdateGroupNameSvc(ctx context.Context, req *types.UpdateGroupNameRequest) (res *types.UpdateGroupNameResponse, err error) { + groupId := req.Id + personId := req.PersonId + groupName := req.Name + groupPubName := req.PublicName + + group, err := s.GetGroupInfoByGroupId(ctx, groupId) + if err != nil { + return nil, err + } + + person, err := s.GetPersonByMemberIdAndGroupId(ctx, personId, groupId) + if err != nil { + return nil, err + } + if err = person.IsAdmin(); err != nil { + return nil, err + } + + if err := s.UpdateGroupName(ctx, group, groupName, groupPubName); err != nil { + return nil, err + } + + return res, nil +} + +// updateGroupInfoName 更新群名称 +func (s *Service) updateGroupInfoName(ctx context.Context, groupId int64, name, publicName string) error { + err := s.dao.UpdateGroupInfoName(ctx, groupId, name, publicName) + if err != nil { + return err + } + return nil +} + +func (s *Service) UpdateGroupName(ctx context.Context, group *biz.GroupInfo, name, pubName string) error { + log := s.GetLogWithTrace(ctx) + groupId := group.GroupId + groupNameAlert := name + groupName := name + groupPubName := pubName + + if groupName == "" { + groupName = group.GroupName + } + + if groupPubName == "" { + groupPubName = group.GroupPubName + } + + if err := s.updateGroupInfoName(ctx, groupId, groupName, groupPubName); err != nil { + return err + } + + // 发送给 pusher + if err := s.PusherSignalGroupName(ctx, groupId, groupNameAlert); err != nil { + log.Error().Err(err).Msg("UpdateGroupNameSvc pusher") + } + + // 发送给 alert + if err := s.NoticeMsgUpdateGroupName(ctx, groupId, s.GetOpe(ctx), groupNameAlert); err != nil { + log.Error().Err(err).Msg("UpdateGroupNameSvc alert") + } + + return nil +} diff --git a/service/group/service/updatemembername.go b/service/group/service/updatemembername.go new file mode 100644 index 0000000..abd8e0e --- /dev/null +++ b/service/group/service/updatemembername.go @@ -0,0 +1,51 @@ +package service + +import ( + "context" + "gitlab.33.cn/chat/dtalk/service/group/model/biz" + + "gitlab.33.cn/chat/dtalk/service/group/model/types" +) + +// UpdateMemberNameSvc 更新群成员昵称 +func (s *Service) UpdateMemberNameSvc(ctx context.Context, req *types.UpdateGroupMemberNameRequest) (res *types.UpdateGroupMemberNameResponse, err error) { + groupId := req.Id + personId := req.PersonId + memberName := req.MemberName + + group, err := s.GetGroupInfoByGroupId(ctx, groupId) + if err != nil { + return nil, err + } + + person, err := s.GetPersonByMemberIdAndGroupId(ctx, personId, groupId) + if err != nil { + return nil, err + } + + if err := s.UpdateMemberName(ctx, group, person, memberName); err != nil { + return nil, err + } + + return res, nil +} + +//updateGroupMemberName 更新群成员昵称 +func (s *Service) updateGroupMemberName(groupId int64, memberId, memberName string) error { + err := s.dao.UpdateGroupMemberName(groupId, memberId, memberName) + if err != nil { + return err + } + return nil +} + +func (s *Service) UpdateMemberName(ctx context.Context, group *biz.GroupInfo, person *biz.GroupMember, memberName string) error { + groupId := group.GroupId + personId := person.GroupMemberId + + if err := s.updateGroupMemberName(groupId, personId, memberName); err != nil { + return err + } + + return nil +} diff --git a/service/group/service/updatemembersmutetime.go b/service/group/service/updatemembersmutetime.go new file mode 100644 index 0000000..5241190 --- /dev/null +++ b/service/group/service/updatemembersmutetime.go @@ -0,0 +1,159 @@ +package service + +import ( + "context" + + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/service/group/model/biz" + "gitlab.33.cn/chat/dtalk/service/group/model/db" + "gitlab.33.cn/chat/dtalk/service/group/model/types" +) + +// UpdateMembersMuteTimeSvc 设置群成员禁言时间 +func (s *Service) UpdateMembersMuteTimeSvc(ctx context.Context, req *types.UpdateGroupMemberMuteTimeRequest) (res *types.UpdateGroupMemberMuteTimeResponse, err error) { + groupId := req.Id + memberIds := req.MemberIds + personId := req.PersonId + muteTime := req.MuteTime + //nowTime := s.getNowTime() + //log := s.GetLogWithTrace(ctx) + var members []*biz.GroupMember + + //if muteTime != biz.MuteMaximum { + // if muteTime > (24 * 60 * 60 * 1000) { + // return nil, xerror.NewError(xerror.ParamsError) + // } + // if muteTime != 0 { + // muteTime += nowTime + // } + //} + + group, err := s.GetGroupInfoByGroupId(ctx, groupId) + if err != nil { + return nil, err + } + + person, err := s.GetPersonByMemberIdAndGroupId(ctx, personId, groupId) + if err != nil { + return nil, err + } + if err = person.IsAdmin(); err != nil { + return nil, err + } + + // 过滤 + for _, memberId := range memberIds { + member, err := s.GetMemberByMemberIdAndGroupId(ctx, memberId, groupId) + if err != nil { + return nil, err + } + if err := member.IsAdmin(); err == nil { + return nil, xerror.NewError(xerror.GroupMutePermission) + } + + members = append(members, member) + } + + members, err = s.UpdateMembersMuteTime(ctx, group, members, muteTime) + if err != nil { + return nil, err + } + + //groupMemberMute := make([]*db.GroupMemberMute, len(memberIds), len(memberIds)) + //for i, memberId := range memberIds { + // groupMemberMute[i] = &db.GroupMemberMute{ + // GroupId: groupId, + // GroupMemberId: memberId, + // GroupMemberMuteTime: muteTime, + // GroupMemberMuteUpdateTime: nowTime, + // } + //} + //if err = s.execSetMemberMuteTimes(ctx, groupMemberMute); err != nil { + // return nil, err + //} + // + //// 发送给 pusher + //if err = s.PusherSignalMemberMuteTime(ctx, groupId, memberIds, muteTime); err != nil { + // log.Error().Err(err).Msg("UpdateMembersMuteTimeSvc pusher") + //} + // + //// 发送给 alert + //if muteTime != 0 { + // if err = s.NoticeMsgUpdateGroupMemberMutedTime(ctx, groupId, personId, memberIds); err != nil { + // log.Error().Err(err).Msg("UpdateMembersMuteTimeSvc alert") + // } + //} + + res = &types.UpdateGroupMemberMuteTimeResponse{ + Members: make([]*types.GroupMember, 0, len(members)), + } + + for _, member := range members { + res.Members = append(res.Members, member.ToTypes()) + } + + return res, nil +} + +// execSetMemberMuteTimes 执行设置群员禁言 +func (s *Service) execSetMemberMuteTimes(ctx context.Context, memberMutes []*db.GroupMemberMute) error { + tx, err := s.dao.NewTx() + if err != nil { + return err + } + defer tx.RollBack() + if err = s.dao.UpdateGroupMemberMuteTimes(ctx, tx, memberMutes); err != nil { + return err + } + + if err = tx.Commit(); err != nil { + return err + } + + return nil +} + +func (s *Service) UpdateMembersMuteTime(ctx context.Context, group *biz.GroupInfo, members []*biz.GroupMember, muteTime int64) ([]*biz.GroupMember, error) { + nowTime := s.getNowTime() + groupId := group.GroupId + log := s.GetLogWithTrace(ctx) + var memberIds []string + + if muteTime != biz.MuteMaximum { + if muteTime > (24 * 60 * 60 * 1000) { + return nil, xerror.NewError(xerror.ParamsError) + } + if muteTime != 0 { + muteTime += nowTime + } + } + + groupMemberMutes := make([]*db.GroupMemberMute, 0, len(members)) + for _, member := range members { + groupMemberMutes = append(groupMemberMutes, &db.GroupMemberMute{ + GroupId: groupId, + GroupMemberId: member.GroupMemberId, + GroupMemberMuteTime: muteTime, + }) + memberIds = append(memberIds, member.GroupMemberId) + member.GroupMemberMuteTime = muteTime + } + + if err := s.execSetMemberMuteTimes(ctx, groupMemberMutes); err != nil { + return nil, err + } + + // 发送给 pusher + if err := s.PusherSignalMemberMuteTime(ctx, groupId, memberIds, muteTime); err != nil { + log.Error().Err(err).Msg("UpdateMembersMuteTimeSvc pusher") + } + + // 发送给 alert + if muteTime != 0 { + if err := s.NoticeMsgUpdateGroupMemberMutedTime(ctx, groupId, s.GetOpe(ctx), memberIds); err != nil { + log.Error().Err(err).Msg("UpdateMembersMuteTimeSvc alert") + } + } + + return members, nil +} diff --git a/service/offline-push/CHANGELOG.md b/service/offline-push/CHANGELOG.md new file mode 100644 index 0000000..141a6ab --- /dev/null +++ b/service/offline-push/CHANGELOG.md @@ -0,0 +1,19 @@ +版本号`major.minor.patch`具体规则如下: +- major:主版本号,如有重大版本重构则该字段递增,通常各主版本间接口不兼容。 +- minor:次版本号,各次版本号间接口保持兼容,如有接口新增或优化则该字段递增。 +- patch:补丁号,如有功能改善或缺陷修复则该字段递增。 + +## version 0.0.2 + +init offline-push + + +## example x.x.x @yy.mm.dd + +**Feature** + +**Bug Fixes** + +**Improvement** + +**Breaking Change** diff --git a/service/offline-push/Makefile b/service/offline-push/Makefile new file mode 100644 index 0000000..294be52 --- /dev/null +++ b/service/offline-push/Makefile @@ -0,0 +1,37 @@ +# golang1.9 or latest +# 1. make help +# 2. make dep +# 3. make build +# ... + +VERSION := $(shell echo $(shell cat version.go | grep "Version =" | cut -d '=' -f2)) +APP_NAME := offline-push +BUILD_DIR := build +APP := ${BUILD_DIR}/${APP_NAME}_v${VERSION} +PKG_NAME := ${APP_NAME}_v${VERSION} +PKG := ${PKG_NAME}.tar.gz + +LDFLAGS := -ldflags "-w -s -X gitlab.33.cn/chat/dtalk/service/$(APP_NAME).GitCommit=`git rev-parse --short=8 HEAD`" +LDGRPC := -ldflags "-X google.golang.org/protobuf/reflect/protoregistry.conflictPolicy=warn" + +.PHONY: clean build pkg + +clean: ## Remove previous build + @rm -rf ${BUILD_DIR} + @go clean + +build: #checkgofmt ## Build the binary file + GOOS=linux GOARCH=amd64 GO111MODULE=on GOPROXY=https://goproxy.cn,direct GOSUMDB="sum.golang.google.cn" go build -v $(LDGRPC) $(LDFLAGS) -o $(APP) cmd/main.go + +pkg: build ## Package + mkdir -p ${PKG_NAME}/bin + mkdir -p ${PKG_NAME}/etc + cp ${APP} ${PKG_NAME}/bin/ + cp etc/* ${PKG_NAME}/etc/ + tar zvcf ${PKG} ${PKG_NAME} + rm -rf ${PKG_NAME} + +REMOTE_BIN_PATH := /opt/dtalk/srv/app/bin +upload: build + rsync -r $(APP) 107:$(REMOTE_BIN_PATH) + ssh 107 "cd $(REMOTE_BIN_PATH) && chmod 777 $(PKG_NAME) && ln -sf $(PKG_NAME) $(APP_NAME) && supervisorctl restart $(APP_NAME)" \ No newline at end of file diff --git a/service/offline-push/api/api.pb.go b/service/offline-push/api/api.pb.go new file mode 100644 index 0000000..0e3bb26 --- /dev/null +++ b/service/offline-push/api/api.pb.go @@ -0,0 +1,268 @@ +// protoc -I=. -I=$GOPATH/src --go_out=plugins=grpc:. *.proto + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc v3.19.1 +// source: api.proto + +package offlinepush + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Device int32 + +const ( + Device_Android Device = 0 + Device_IOS Device = 1 +) + +// Enum value maps for Device. +var ( + Device_name = map[int32]string{ + 0: "Android", + 1: "IOS", + } + Device_value = map[string]int32{ + "Android": 0, + "IOS": 1, + } +) + +func (x Device) Enum() *Device { + p := new(Device) + *p = x + return p +} + +func (x Device) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Device) Descriptor() protoreflect.EnumDescriptor { + return file_api_proto_enumTypes[0].Descriptor() +} + +func (Device) Type() protoreflect.EnumType { + return &file_api_proto_enumTypes[0] +} + +func (x Device) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Device.Descriptor instead. +func (Device) EnumDescriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{0} +} + +// record --> mq +type OffPushMsg struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + AppId string `protobuf:"bytes,1,opt,name=appId,proto3" json:"appId,omitempty"` + Device Device `protobuf:"varint,2,opt,name=device,proto3,enum=dtalk.offline_push.Device" json:"device,omitempty"` + Title string `protobuf:"bytes,3,opt,name=title,proto3" json:"title,omitempty"` + Content string `protobuf:"bytes,4,opt,name=content,proto3" json:"content,omitempty"` + Token string `protobuf:"bytes,5,opt,name=token,proto3" json:"token,omitempty"` + ChannelType int32 `protobuf:"varint,6,opt,name=channelType,proto3" json:"channelType,omitempty"` + Target string `protobuf:"bytes,7,opt,name=target,proto3" json:"target,omitempty"` + Timeout int64 `protobuf:"varint,8,opt,name=timeout,proto3" json:"timeout,omitempty"` +} + +func (x *OffPushMsg) Reset() { + *x = OffPushMsg{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OffPushMsg) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OffPushMsg) ProtoMessage() {} + +func (x *OffPushMsg) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OffPushMsg.ProtoReflect.Descriptor instead. +func (*OffPushMsg) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{0} +} + +func (x *OffPushMsg) GetAppId() string { + if x != nil { + return x.AppId + } + return "" +} + +func (x *OffPushMsg) GetDevice() Device { + if x != nil { + return x.Device + } + return Device_Android +} + +func (x *OffPushMsg) GetTitle() string { + if x != nil { + return x.Title + } + return "" +} + +func (x *OffPushMsg) GetContent() string { + if x != nil { + return x.Content + } + return "" +} + +func (x *OffPushMsg) GetToken() string { + if x != nil { + return x.Token + } + return "" +} + +func (x *OffPushMsg) GetChannelType() int32 { + if x != nil { + return x.ChannelType + } + return 0 +} + +func (x *OffPushMsg) GetTarget() string { + if x != nil { + return x.Target + } + return "" +} + +func (x *OffPushMsg) GetTimeout() int64 { + if x != nil { + return x.Timeout + } + return 0 +} + +var File_api_proto protoreflect.FileDescriptor + +var file_api_proto_rawDesc = []byte{ + 0x0a, 0x09, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x64, 0x74, 0x61, + 0x6c, 0x6b, 0x2e, 0x6f, 0x66, 0x66, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x70, 0x75, 0x73, 0x68, 0x22, + 0xf0, 0x01, 0x0a, 0x0a, 0x4f, 0x66, 0x66, 0x50, 0x75, 0x73, 0x68, 0x4d, 0x73, 0x67, 0x12, 0x14, + 0x0a, 0x05, 0x61, 0x70, 0x70, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, + 0x70, 0x70, 0x49, 0x64, 0x12, 0x32, 0x0a, 0x06, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x64, 0x74, 0x61, 0x6c, 0x6b, 0x2e, 0x6f, 0x66, 0x66, + 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x70, 0x75, 0x73, 0x68, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x52, 0x06, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x18, + 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, + 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x20, + 0x0a, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x16, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, + 0x6f, 0x75, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, + 0x75, 0x74, 0x2a, 0x1e, 0x0a, 0x06, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x0b, 0x0a, 0x07, + 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x49, 0x4f, 0x53, + 0x10, 0x01, 0x42, 0x2d, 0x5a, 0x2b, 0x67, 0x69, 0x74, 0x6c, 0x61, 0x62, 0x2e, 0x33, 0x33, 0x2e, + 0x63, 0x6e, 0x2f, 0x63, 0x68, 0x61, 0x74, 0x2f, 0x64, 0x74, 0x61, 0x6c, 0x6b, 0x2f, 0x73, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x6f, 0x66, 0x66, 0x6c, 0x69, 0x6e, 0x65, 0x70, 0x75, 0x73, + 0x68, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_api_proto_rawDescOnce sync.Once + file_api_proto_rawDescData = file_api_proto_rawDesc +) + +func file_api_proto_rawDescGZIP() []byte { + file_api_proto_rawDescOnce.Do(func() { + file_api_proto_rawDescData = protoimpl.X.CompressGZIP(file_api_proto_rawDescData) + }) + return file_api_proto_rawDescData +} + +var file_api_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_api_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_api_proto_goTypes = []interface{}{ + (Device)(0), // 0: dtalk.offline_push.Device + (*OffPushMsg)(nil), // 1: dtalk.offline_push.OffPushMsg +} +var file_api_proto_depIdxs = []int32{ + 0, // 0: dtalk.offline_push.OffPushMsg.device:type_name -> dtalk.offline_push.Device + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_api_proto_init() } +func file_api_proto_init() { + if File_api_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_api_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OffPushMsg); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_api_proto_rawDesc, + NumEnums: 1, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_api_proto_goTypes, + DependencyIndexes: file_api_proto_depIdxs, + EnumInfos: file_api_proto_enumTypes, + MessageInfos: file_api_proto_msgTypes, + }.Build() + File_api_proto = out.File + file_api_proto_rawDesc = nil + file_api_proto_goTypes = nil + file_api_proto_depIdxs = nil +} diff --git a/service/offline-push/api/api.proto b/service/offline-push/api/api.proto new file mode 100644 index 0000000..681cc18 --- /dev/null +++ b/service/offline-push/api/api.proto @@ -0,0 +1,22 @@ +// protoc -I=. -I=$GOPATH/src --go_out=plugins=grpc:. *.proto +syntax = "proto3"; + +package dtalk.offline_push; +option go_package = "gitlab.33.cn/chat/dtalk/service/offlinepush"; + +enum Device { + Android = 0; + IOS = 1; +} + +// record --> mq +message OffPushMsg { + string appId = 1; + Device device = 2; + string title = 3; + string content = 4; + string token = 5; + int32 channelType = 6; + string target = 7; + int64 timeout = 8; +} \ No newline at end of file diff --git a/service/offline-push/api/create.sh b/service/offline-push/api/create.sh new file mode 100644 index 0000000..0e5dbf9 --- /dev/null +++ b/service/offline-push/api/create.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +protoc -I. -I$GOPATH/src \ + --go_out=. --go_opt=paths=source_relative \ + --go-grpc_out=. --go-grpc_opt=paths=source_relative \ + *.proto \ No newline at end of file diff --git a/service/offline-push/cmd/main.go b/service/offline-push/cmd/main.go new file mode 100644 index 0000000..3ef23e6 --- /dev/null +++ b/service/offline-push/cmd/main.go @@ -0,0 +1,87 @@ +package main + +import ( + "flag" + "fmt" + "os" + "os/signal" + "syscall" + "time" + + "github.com/inconshreveable/log15" + "gitlab.33.cn/chat/dtalk/service/offline-push/config" + _ "gitlab.33.cn/chat/dtalk/service/offline-push/pusher/android" + _ "gitlab.33.cn/chat/dtalk/service/offline-push/pusher/ios" + "gitlab.33.cn/chat/dtalk/service/offline-push/service" +) + +const srvName = "offline-push" + +var log = log15.New("cmd", srvName) + +var ( + // projectVersion 项目版本 + projectVersion = "0.0.2" + // goVersion go版本 + goVersion = "" + // gitCommit git提交commit id + gitCommit = "" + // buildTime 编译时间 + buildTime = "" + + isShowVersion = flag.Bool("v", false, "show project version") +) + +// showVersion 显示项目版本信息 +func showVersion(isShow bool) { + if isShow { + fmt.Printf("Project: %s\n", srvName) + fmt.Printf(" Version: %s\n", projectVersion) + fmt.Printf(" Go Version: %s\n", goVersion) + fmt.Printf(" Git Commit: %s\n", gitCommit) + fmt.Printf(" Build Time: %s\n", buildTime) + os.Exit(0) + } +} + +// @Title 聊天单模块集成测试 +// @Version 0.1 +// @Description +// @SecurityDefinitions.ApiKey ApiKeyAuth +// @In header +// @Name Authorization +// @BasePath / +func main() { + flag.Parse() + showVersion(*isShowVersion) + + if err := config.Init(); err != nil { + panic(err) + } + log.Info("config info:", + "AppId", config.Conf.AppId, + "Pushers Android", config.Conf.Pushers["Android"], + "Pushers iOS", config.Conf.Pushers["iOS"], + "MQSub", config.Conf.MQSub) + // service init + svc := service.New(config.Conf) + svc.ListenMQ() + + // init signal + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT) + for { + s := <-c + log.Info("service get a signal %s", s.String()) + switch s { + case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT: + time.Sleep(time.Second * 2) + log.Info(srvName + " server exit") + return + case syscall.SIGHUP: + // TODO reload + default: + return + } + } +} diff --git a/service/offline-push/config/config.go b/service/offline-push/config/config.go new file mode 100644 index 0000000..c4da610 --- /dev/null +++ b/service/offline-push/config/config.go @@ -0,0 +1,53 @@ +package config + +import ( + "flag" + "github.com/BurntSushi/toml" +) + +var ( + confPath string + + // Conf config + Conf *Config +) + +func init() { + flag.StringVar(&confPath, "conf", "offline-push.toml", "default config path.") +} + +// Init init config. +func Init() (err error) { + Conf = Default() + _, err = toml.DecodeFile(confPath, &Conf) + return +} + +// Default new a config with specified defualt value. +func Default() *Config { + return &Config{ + MQSub: &MQSubClient{ + Brokers: nil, + Number: 0, + }, + } +} + +type Config struct { + //push config + AppId string + Pushers map[string]*Pusher + MQSub *MQSubClient +} + +type Pusher struct { + Env string + AppKey string + AppMasterSecret string + MiActivity string +} + +type MQSubClient struct { + Brokers []string + Number uint32 +} diff --git a/service/offline-push/config/offline-push.toml b/service/offline-push/config/offline-push.toml new file mode 100644 index 0000000..6f45241 --- /dev/null +++ b/service/offline-push/config/offline-push.toml @@ -0,0 +1,22 @@ +AppId= "dtalk" + +[server] +addr="0.0.0.0:18100" + +[MQSub] +Brokers=["127.0.0.1:9092"] +Number=16 + +# 友盟离线推送物料 +[pushers] + [pushers.Android] + Env = "debug" + AppKey = "" + AppMasterSecret = "" + MiActivity = "" + + [pushers.iOS] + Env = "debug" + AppKey = "" + AppMasterSecret = "" + MiActivity = "" \ No newline at end of file diff --git a/service/offline-push/model/error.go b/service/offline-push/model/error.go new file mode 100644 index 0000000..d653157 --- /dev/null +++ b/service/offline-push/model/error.go @@ -0,0 +1,9 @@ +package model + +import "errors" + +var ( + ErrAppId = errors.New("appId not compared") + ErrConsumeRedo = errors.New("process msg failed") + ErrCustomNotSupport = errors.New("pusher device type not support") +) diff --git a/service/offline-push/pusher/android/plugin.go b/service/offline-push/pusher/android/plugin.go new file mode 100644 index 0000000..a87a131 --- /dev/null +++ b/service/offline-push/pusher/android/plugin.go @@ -0,0 +1,18 @@ +package android + +import "gitlab.33.cn/chat/dtalk/service/offline-push/pusher" + +const Name = "Android" + +func init() { + pusher.Register(Name, New) +} + +func New(cfg pusher.Config) pusher.IPusher { + return &androidPusher{ + AppKey: cfg.AppKey, + AppMasterSecret: cfg.AppMasterSecret, + MiActivity: cfg.MiActivity, + environment: cfg.Environment, + } +} diff --git a/service/offline-push/pusher/android/push.go b/service/offline-push/pusher/android/push.go new file mode 100644 index 0000000..a4144c5 --- /dev/null +++ b/service/offline-push/pusher/android/push.go @@ -0,0 +1,74 @@ +package android + +import ( + "errors" + "gitlab.33.cn/chat/dtalk/pkg/util" + "strconv" + + push "github.com/oofpgDLD/u-push" + android_push "github.com/oofpgDLD/u-push/android" + "gitlab.33.cn/chat/dtalk/service/offline-push/pusher" +) + +type androidPusher struct { + AppKey string + AppMasterSecret string + MiActivity string + environment string +} + +func (t *androidPusher) SinglePush(deviceToken, title, text string, extra *pusher.Extra) error { + var client push.PushClient + unicast := android_push.NewAndroidUnicast(t.AppKey, t.AppMasterSecret) + + //fmt.Println(t.AppKey, t.AppMasterSecret, t.DeviceToken, title, text) + unicast.SetDeviceToken(deviceToken) + unicast.SetTitle(title) + unicast.SetText(text) + unicast.GoCustomAfterOpen("") + unicast.SetDisplayType(push.NOTIFICATION) + unicast.SetMipush(true, t.MiActivity) + unicast.SetExpireTime(util.UnixToTime(extra.TimeOutTime).In(util.Shanghai()).Format("2006-01-02 15:04:05")) + switch t.environment { + case "debug": + // 测试模式 + unicast.SetTestMode() + case "release": + // 线上模式 + unicast.SetReleaseMode() + default: + return errors.New("unknown environment") + } + // Set customized fields + unicast.SetExtraField("address", extra.Address) + unicast.SetExtraField("channelType", strconv.FormatInt(int64(extra.ChannelType), 10)) + return client.Send(unicast) +} + +func (t *androidPusher) SingleCustomPush(address, title, text string, extra *pusher.Extra) error { + var client push.PushClient + unicast := android_push.NewAndroidCustomizedcast(t.AppKey, t.AppMasterSecret) + + //fmt.Println(t.AppKey, t.AppMasterSecret, t.DeviceToken, title, text) + unicast.SetAlias(address, "ADDRESS") + unicast.SetTitle(title) + unicast.SetText(text) + unicast.GoCustomAfterOpen("") + unicast.SetDisplayType(push.NOTIFICATION) + unicast.SetMipush(true, t.MiActivity) + unicast.SetExpireTime(util.UnixToTime(extra.TimeOutTime).In(util.Shanghai()).Format("2006-01-02 15:04:05")) + switch t.environment { + case "debug": + // 测试模式 + unicast.SetTestMode() + case "release": + // 线上模式 + unicast.SetReleaseMode() + default: + return errors.New("unknown environment") + } + // Set customized fields + unicast.SetExtraField("address", extra.Address) + unicast.SetExtraField("channelType", strconv.FormatInt(int64(extra.ChannelType), 10)) + return client.Send(unicast) +} diff --git a/service/offline-push/pusher/android/push_test.go b/service/offline-push/pusher/android/push_test.go new file mode 100644 index 0000000..1f45661 --- /dev/null +++ b/service/offline-push/pusher/android/push_test.go @@ -0,0 +1,60 @@ +package android + +import ( + "gitlab.33.cn/chat/dtalk/service/offline-push/pusher" + "testing" +) + +func Test_androidPusher_SinglePush(t1 *testing.T) { + type fields struct { + AppKey string + AppMasterSecret string + MiActivity string + environment string + } + type args struct { + deviceToken string + title string + text string + extra *pusher.Extra + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "test android offline push", + fields: fields{ + AppKey: "606ebf176a23f17dcf15b2cd", + AppMasterSecret: "uengh9mzrvm5zdclyt5ean05ckqc2lxl", + MiActivity: "", + environment: "debug", + }, + args: args{ + deviceToken: "Apjwo_0X0-y0sGcWPxzGrY1dl2qvv_uE7LAeCoivoHjf", + title: "测试title", + text: "测试text", + extra: &pusher.Extra{ + Address: "1FdnxKR4r952x2HQA2BTTpFH6tgHYYNs3M", + ChannelType: 0, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t1.Run(tt.name, func(t1 *testing.T) { + t := &androidPusher{ + AppKey: tt.fields.AppKey, + AppMasterSecret: tt.fields.AppMasterSecret, + MiActivity: tt.fields.MiActivity, + environment: tt.fields.environment, + } + if err := t.SinglePush(tt.args.deviceToken, tt.args.title, tt.args.text, tt.args.extra); (err != nil) != tt.wantErr { + t1.Errorf("SinglePush() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/service/offline-push/pusher/ios/plugin.go b/service/offline-push/pusher/ios/plugin.go new file mode 100644 index 0000000..6f6b650 --- /dev/null +++ b/service/offline-push/pusher/ios/plugin.go @@ -0,0 +1,18 @@ +package ios + +import "gitlab.33.cn/chat/dtalk/service/offline-push/pusher" + +const Name = "iOS" + +func init() { + pusher.Register(Name, New) +} + +func New(cfg pusher.Config) pusher.IPusher { + return &iOSPusher{ + AppKey: cfg.AppKey, + AppMasterSecret: cfg.AppMasterSecret, + MiActivity: cfg.MiActivity, + environment: cfg.Environment, + } +} diff --git a/service/offline-push/pusher/ios/push.go b/service/offline-push/pusher/ios/push.go new file mode 100644 index 0000000..d82d0f6 --- /dev/null +++ b/service/offline-push/pusher/ios/push.go @@ -0,0 +1,82 @@ +package ios + +import ( + "errors" + "gitlab.33.cn/chat/dtalk/pkg/util" + "strconv" + + push "github.com/oofpgDLD/u-push" + ios_push "github.com/oofpgDLD/u-push/ios" + "gitlab.33.cn/chat/dtalk/service/offline-push/pusher" +) + +type iOSPusher struct { + AppKey string + AppMasterSecret string + MiActivity string + environment string +} + +func (t *iOSPusher) SinglePush(deviceToken, title, text string, extra *pusher.Extra) error { + var client push.PushClient + unicast := ios_push.NewIOSUnicast(t.AppKey, t.AppMasterSecret) + + //fmt.Println(t.AppKey, t.AppMasterSecret, t.DeviceToken, title, text) + unicast.SetDeviceToken(deviceToken) + + //unicast.SetAlert("IOS 单播测试") + unicast.SetAlertJson(push.IOSAlert{ + Title: title, + Body: text, + }) + //unicast.SetBadge(0) + unicast.SetSound("default") + unicast.SetExpireTime(util.UnixToTime(extra.TimeOutTime).In(util.Shanghai()).Format("2006-01-02 15:04:05")) + + switch t.environment { + case "debug": + // 测试模式 + unicast.SetTestMode() + case "release": + // 线上模式 + unicast.SetReleaseMode() + default: + return errors.New("unknown environment") + } + // Set customized fields + unicast.SetCustomizedField("address", extra.Address) + unicast.SetCustomizedField("channelType", strconv.FormatInt(int64(extra.ChannelType), 10)) + return client.Send(unicast) +} + +func (t *iOSPusher) SingleCustomPush(address, title, text string, extra *pusher.Extra) error { + var client push.PushClient + unicast := ios_push.NewIOSCustomizedcast(t.AppKey, t.AppMasterSecret) + + //fmt.Println(t.AppKey, t.AppMasterSecret, t.DeviceToken, title, text) + unicast.SetAlias(address, "ADDRESS") + + //unicast.SetAlert("IOS 单播测试") + unicast.SetAlertJson(push.IOSAlert{ + Title: title, + Body: text, + }) + //unicast.SetBadge(0) + unicast.SetSound("default") + unicast.SetExpireTime(util.UnixToTime(extra.TimeOutTime).In(util.Shanghai()).Format("2006-01-02 15:04:05")) + + switch t.environment { + case "debug": + // 测试模式 + unicast.SetTestMode() + case "release": + // 线上模式 + unicast.SetReleaseMode() + default: + return errors.New("unknown environment") + } + // Set customized fields + unicast.SetCustomizedField("address", extra.Address) + unicast.SetCustomizedField("channelType", strconv.FormatInt(int64(extra.ChannelType), 10)) + return client.Send(unicast) +} diff --git a/service/offline-push/pusher/push.go b/service/offline-push/pusher/push.go new file mode 100644 index 0000000..6ef20a6 --- /dev/null +++ b/service/offline-push/pusher/push.go @@ -0,0 +1,44 @@ +package pusher + +import ( + "encoding/json" + "errors" +) + +var execPusher = make(map[string]CreateFunc) + +type CreateFunc func(cfg Config) IPusher + +func Register(name string, exec CreateFunc) { + execPusher[name] = exec +} + +func Load(name string) (CreateFunc, error) { + exec, ok := execPusher[name] + if !ok { + return nil, errors.New("pusher not find") + } + return exec, nil +} + +type Config struct { + AppKey string + AppMasterSecret string + MiActivity string + Environment string +} + +type IPusher interface { + SinglePush(deviceToken, title, text string, extra *Extra) error + SingleCustomPush(address, title, text string, extra *Extra) error +} + +type Extra struct { + Address string `json:"address"` + ChannelType int32 `json:"channelType"` + TimeOutTime int64 `json:"-"` +} + +func (e *Extra) ToBytes() ([]byte, error) { + return json.Marshal(e) +} diff --git a/service/offline-push/service/kafka/consumer.go b/service/offline-push/service/kafka/consumer.go new file mode 100644 index 0000000..b2e038a --- /dev/null +++ b/service/offline-push/service/kafka/consumer.go @@ -0,0 +1,49 @@ +package kafka + +import ( + cluster "github.com/bsm/sarama-cluster" + "github.com/golang/protobuf/proto" + "github.com/inconshreveable/log15" + offlinepush "gitlab.33.cn/chat/dtalk/service/offline-push/api" + "gitlab.33.cn/chat/dtalk/service/offline-push/model" +) + +var log = log15.New("model", "offputhtest") + +type Process interface { + Deal(m *offlinepush.OffPushMsg) error +} + +type Consumer struct { + *cluster.Consumer +} + +func (c *Consumer) Listen(p Process) { + for { + select { + case err := <-c.Errors(): + log.Error("consumer error", "err", err) + case n := <-c.Notifications(): + log.Info("consumer rebalanced", "number", n) + case msg, ok := <-c.Messages(): + if !ok { + log.Debug("consume not ok", "topic", msg.Topic, "partition", msg.Partition, "offset", msg.Offset, "key", msg.Key) + return + } + bizMsg := new(offlinepush.OffPushMsg) + if err := proto.Unmarshal(msg.Value, bizMsg); err != nil { + log.Error("proto.Unmarshal error", "err", err, "msg", msg) + continue + } + log.Debug("consume process", "topic", msg.Topic, "partition", msg.Partition, "offset", msg.Offset, "key", msg.Key, "bizMsg", bizMsg) + err := p.Deal(bizMsg) + if err != nil { + log.Debug("p.Deal error", "err", err, "bizMsg", bizMsg) + if err == model.ErrConsumeRedo { + //TODO redo consume message + } + } + c.MarkOffset(msg, "") + } + } +} diff --git a/service/offline-push/service/kafka/creator.go b/service/offline-push/service/kafka/creator.go new file mode 100644 index 0000000..c8f8a1b --- /dev/null +++ b/service/offline-push/service/kafka/creator.go @@ -0,0 +1,34 @@ +package kafka + +import ( + "fmt" + cluster "github.com/bsm/sarama-cluster" + "gitlab.33.cn/chat/dtalk/service/offline-push/config" + "strconv" +) + +func NewKafkaConsumers(appId string, cfg *config.MQSubClient, groupIdx int) map[string]*Consumer { + store := make(map[string]*Consumer) + num := int(cfg.Number) + for i := 0; i < num; i++ { + store[strconv.Itoa(i)] = &Consumer{Consumer: newKafkaSub(appId, groupIdx, cfg.Brokers)} + } + return store +} + +func newKafkaSub(appId string, groupIdx int, brokers []string) *cluster.Consumer { + c := cluster.NewConfig() + c.Consumer.Return.Errors = true + c.Group.Return.Notifications = true + + topic := fmt.Sprintf("offpush-%s-topic", appId) + group := fmt.Sprintf("offpush-%s-group", appId) + if groupIdx > 0 { + group = fmt.Sprintf("%s-%d", group, groupIdx) + } + consumer, err := cluster.NewConsumer(brokers, group, []string{topic}, c) + if err != nil { + panic(err) + } + return consumer +} diff --git a/service/offline-push/service/service.go b/service/offline-push/service/service.go new file mode 100644 index 0000000..db442a8 --- /dev/null +++ b/service/offline-push/service/service.go @@ -0,0 +1,95 @@ +package service + +import ( + "fmt" + "time" + + "github.com/inconshreveable/log15" + offlinepush "gitlab.33.cn/chat/dtalk/service/offline-push/api" + "gitlab.33.cn/chat/dtalk/service/offline-push/config" + "gitlab.33.cn/chat/dtalk/service/offline-push/model" + "gitlab.33.cn/chat/dtalk/service/offline-push/pusher" + "gitlab.33.cn/chat/dtalk/service/offline-push/pusher/android" + "gitlab.33.cn/chat/dtalk/service/offline-push/pusher/ios" + "gitlab.33.cn/chat/dtalk/service/offline-push/service/kafka" +) + +type Service struct { + log log15.Logger + cfg *config.Config + consumers map[string]*kafka.Consumer + pushers map[offlinepush.Device]pusher.IPusher +} + +func New(c *config.Config) *Service { + s := &Service{ + log: log15.New("module", "offline-push/service"), + cfg: c, + consumers: kafka.NewKafkaConsumers(c.AppId, c.MQSub, 0), + pushers: make(map[offlinepush.Device]pusher.IPusher), + } + s.loadPushers() + return s +} + +func (s Service) Config() *config.Config { + return s.cfg +} + +func (s *Service) ListenMQ() { + for i, c := range s.consumers { + s.log.Debug(fmt.Sprintf("accept %v", i)) + go c.Listen(s) + } +} +func (s *Service) loadPushers() { + androidCreator, err := pusher.Load(android.Name) + if err != nil { + panic(err) + } + iOSCreator, err := pusher.Load(ios.Name) + if err != nil { + panic(err) + } + s.pushers[offlinepush.Device_Android] = androidCreator(pusher.Config{ + AppKey: s.cfg.Pushers[android.Name].AppKey, + AppMasterSecret: s.cfg.Pushers[android.Name].AppMasterSecret, + MiActivity: s.cfg.Pushers[android.Name].MiActivity, + Environment: s.cfg.Pushers[android.Name].Env, + }) + s.pushers[offlinepush.Device_IOS] = iOSCreator(pusher.Config{ + AppKey: s.cfg.Pushers[ios.Name].AppKey, + AppMasterSecret: s.cfg.Pushers[ios.Name].AppMasterSecret, + MiActivity: s.cfg.Pushers[ios.Name].MiActivity, + Environment: s.cfg.Pushers[ios.Name].Env, + }) +} + +func (s *Service) Deal(m *offlinepush.OffPushMsg) error { + if m.AppId != s.cfg.AppId { + return model.ErrAppId + } + + p, ok := s.pushers[m.Device] + if !ok { + s.log.Error("pusher exec not find", "deviceType", m.Device.String(), "pushers", s.pushers) + return model.ErrCustomNotSupport + } + if tm := time.Now().Unix(); tm > m.Timeout { + s.log.Info("message offline push timeout", + "appId", m.GetAppId(), + "deviceType", m.GetDevice().String(), + "deviceToken", m.GetToken(), + "channelType", m.GetChannelType(), + "target", m.GetTarget(), + "timeout time", m.GetTimeout(), + "now time", tm, + ) + return nil + } + return p.SinglePush(m.Token, m.Title, m.Content, &pusher.Extra{ + Address: m.Target, + ChannelType: m.ChannelType, + TimeOutTime: m.Timeout, + }) +} diff --git a/service/offline-push/tools/mock/Makefile b/service/offline-push/tools/mock/Makefile new file mode 100644 index 0000000..4341e52 --- /dev/null +++ b/service/offline-push/tools/mock/Makefile @@ -0,0 +1,21 @@ +# golang1.9 or latest +# 1. make help +# 2. make dep +# 3. make build +# ... + +APP_NAME := client +BUILD_DIR := build +APP := ${BUILD_DIR}/${APP_NAME} + +LDFLAGS := -ldflags "-w -s -X gitlab.33.cn/chat/dtalk/version.GitCommit=`git rev-parse --short=8 HEAD`" +LDGRPC := -ldflags "-X google.golang.org/protobuf/reflect/protoregistry.conflictPolicy=warn" + +.PHONY: clean build + +clean: ## Remove previous build + @rm -rf ${BUILD_DIR} + @go clean + +build: #checkgofmt ## Build the binary file + GOOS=linux GOARCH=amd64 GO111MODULE=on GOPROXY=https://goproxy.cn,direct GOSUMDB="sum.golang.google.cn" go build -v $(LDGRPC) $(LDFLAGS) -o $(APP) cmd/main.go diff --git a/service/offline-push/tools/mock/client.go b/service/offline-push/tools/mock/client.go new file mode 100644 index 0000000..19530cc --- /dev/null +++ b/service/offline-push/tools/mock/client.go @@ -0,0 +1,38 @@ +package mock + +import ( + "context" + "fmt" + "gitlab.33.cn/chat/dtalk/service/record/kafka/publisher" + "gopkg.in/Shopify/sarama.v1" + kafka "gopkg.in/Shopify/sarama.v1" +) + +type Client struct { + brokers []string + appId string + offPushPub kafka.SyncProducer +} + +func NewClient(appId string, brokers []string) *Client { + c := &Client{ + appId: appId, + brokers: brokers, + offPushPub: publisher.NewKafkaPub(brokers), + } + return c +} + +func (c *Client) PublishOfflineMsg(ctx context.Context, fromId string, b []byte) error { + if c.offPushPub == nil { + return fmt.Errorf("kafka publish client not init") + } + appTopic := fmt.Sprintf("offpush-%s-topic", c.appId) + m := &sarama.ProducerMessage{ + Key: sarama.StringEncoder(fromId), + Topic: appTopic, + Value: sarama.ByteEncoder(b), + } + _, _, err := c.offPushPub.SendMessage(m) + return err +} diff --git a/service/offline-push/tools/mock/cmd/main.go b/service/offline-push/tools/mock/cmd/main.go new file mode 100644 index 0000000..4d98f74 --- /dev/null +++ b/service/offline-push/tools/mock/cmd/main.go @@ -0,0 +1,31 @@ +package main + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" + "gitlab.33.cn/chat/dtalk/service/offline-push/tools/mock/cmd/push" +) + +var rootCmd = &cobra.Command{ + Use: "tools", + Short: "offline push mock tools", + Example: " tools auth -d \n", +} + +func init() { + rootCmd.AddCommand(push.SinglePushCmd) +} + +// Execute executes the root command and its subcommands. +func Execute() { + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} + +func main() { + Execute() +} diff --git a/service/offline-push/tools/mock/cmd/push/push.go b/service/offline-push/tools/mock/cmd/push/push.go new file mode 100644 index 0000000..811c038 --- /dev/null +++ b/service/offline-push/tools/mock/cmd/push/push.go @@ -0,0 +1,50 @@ +package push + +import ( + "context" + "fmt" + "github.com/spf13/cobra" + offlinepush "gitlab.33.cn/chat/dtalk/service/offline-push/api" + "gitlab.33.cn/chat/dtalk/service/offline-push/tools/mock" + config "gitlab.33.cn/chat/dtalk/service/offline-push/tools/mock/etc" +) + +var SinglePushCmd = &cobra.Command{ + Use: "single", + Short: "single push", + Long: "single push", + Example: "single -n 1 --hm=true -d true", + Run: singlePush, +} + +func init() { + SinglePushCmd.Flags().StringVarP(&config.ConfPath, "conf", "c", "config.toml", "default config path.") +} + +func singlePush(cmd *cobra.Command, args []string) { + err := config.Init() + if err != nil { + fmt.Printf("config init error:%v\n", err) + return + } + fmt.Printf("Client Config: %v\n Msg Config: %v\n", config.Conf.Client, config.Conf.Msg) + c := mock.NewClient(config.Conf.Client.AppId, config.Conf.Client.Brokers) + m := mock.Msg{ + AppId: config.Conf.Msg.AppId, + DeviceType: offlinepush.Device(config.Conf.Msg.DeviceType), + Nickname: config.Conf.Msg.Nickname, + TargetId: config.Conf.Msg.TargetId, + DeviceToken: config.Conf.Msg.DeviceToken, + } + data, err := m.Data() + if err != nil { + fmt.Printf("msg data error:%v\n", err) + return + } + err = c.PublishOfflineMsg(context.Background(), config.Conf.Client.FromId, data) + if err != nil { + fmt.Printf("push msg error:%v\n", err) + return + } + fmt.Println("success") +} diff --git a/service/offline-push/tools/mock/etc/config.go b/service/offline-push/tools/mock/etc/config.go new file mode 100644 index 0000000..48a8277 --- /dev/null +++ b/service/offline-push/tools/mock/etc/config.go @@ -0,0 +1,53 @@ +package config + +import ( + "github.com/BurntSushi/toml" +) + +var ( + ConfPath string + Conf *Config +) + +// Init init config. +func Init() (err error) { + Conf = Default() + _, err = toml.DecodeFile(ConfPath, &Conf) + return +} + +func Default() *Config { + return &Config{ + Client: Client{ + AppId: "", + FromId: "", + Brokers: nil, + }, + Msg: Msg{ + AppId: "", + DeviceType: 0, + Nickname: "", + TargetId: "", + DeviceToken: "", + }, + } +} + +type Config struct { + Client Client + Msg Msg +} + +type Client struct { + AppId string + FromId string + Brokers []string +} + +type Msg struct { + AppId string + DeviceType int32 + Nickname string + TargetId string + DeviceToken string +} diff --git a/service/offline-push/tools/mock/etc/config.toml b/service/offline-push/tools/mock/etc/config.toml new file mode 100644 index 0000000..c0d492c --- /dev/null +++ b/service/offline-push/tools/mock/etc/config.toml @@ -0,0 +1,11 @@ +[client] +AppId = "" +FromId="test" +Brokers=[""] + +[msg] +AppId = "" +DeviceType = 0 +Nickname = "" +TargetId = "" +DeviceToken = "" diff --git a/service/offline-push/tools/mock/msg.go b/service/offline-push/tools/mock/msg.go new file mode 100644 index 0000000..f3b7a72 --- /dev/null +++ b/service/offline-push/tools/mock/msg.go @@ -0,0 +1,31 @@ +package mock + +import ( + "github.com/golang/protobuf/proto" + offlinepush "gitlab.33.cn/chat/dtalk/service/offline-push/api" + xproto "gitlab.33.cn/chat/imparse/proto" + "time" +) + +type Msg struct { + AppId string + DeviceType offlinepush.Device + Nickname string + TargetId string + DeviceToken string +} + +func (m *Msg) Data() ([]byte, error) { + //需要推送 + pushMsg := &offApi.OffPushMsg{ + AppId: m.AppId, + Device: m.DeviceType, + Title: m.Nickname, + Content: "[你收到一条消息]", + Token: m.DeviceToken, + ChannelType: int32(xproto.Channel_ToUser), + Target: m.TargetId, + Timeout: time.Now().Add(time.Minute * 7).Unix(), + } + return proto.Marshal(pushMsg) +} diff --git a/service/offline-push/version.go b/service/offline-push/version.go new file mode 100644 index 0000000..24d065e --- /dev/null +++ b/service/offline-push/version.go @@ -0,0 +1,16 @@ +package version + +//var ( +// // The full version string +// Version = "0.0.2" +// +// // GitCommit is set with --ldflags "-X main.gitCommit=$(git rev-parse --short=8 HEAD)" +// GitCommit string +//) +// +//func GetVersion() string { +// if GitCommit != "" { +// return Version + "-" + GitCommit +// } +// return Version +//} diff --git a/service/oss/CHANGELOG.md b/service/oss/CHANGELOG.md new file mode 100644 index 0000000..93a5e21 --- /dev/null +++ b/service/oss/CHANGELOG.md @@ -0,0 +1,73 @@ +版本号`major.minor.patch`具体规则如下: + +- major:主版本号,如有重大版本重构则该字段递增,通常各主版本间接口不兼容。 +- minor:次版本号,各次版本号间接口保持兼容,如有接口新增或优化则该字段递增。 +- patch:补丁号,如有功能改善或缺陷修复则该字段递增。 + +## version 1.5.1 2022_01_06 + +**Feature** + +- 增加对 key 的验证 +- 上传设置重试次数 + +## version 1.5 + +**Feature** + +- minio 配置文件增加 publicUrl v1.5.0 2021_11_10 + +## version 1.4 + +**Feature** + +- 上传成功接口同时返回 url 和 uri +- 增加 get-host 接口 @v1.4.0 2021.8.30 + +## version 1.3 + +**Feature** + +- 支持 minio @v1.3.0 2021.7.28 +- 简化接口参数, 限制单次上传最大5g, 分片单次上传最小5mb @v1.3.1 2021.8.2 + +**Bug fix** +- 分片上传取消最小文件大小限制 @v1.3.2 2021.8.17 +- 修正取消分片上传接口文档错误 @v1.3.3 2021.8.23 +- minio.getUrl 专门为 zbotc 只返回 uri, 而不是完整 url @v1.3.3.1 2021.8.26 + + +## version 1.2.0 @2021.7.22 + +**Feature** + +- 支持代理文件分段上传 @1.2.0 2021.7.22 +- 更新文档 @v1.2.1 +- 移动 aliyun 和 huaweiyun 至 pkg 文件夹中 @v1.2.2 2021.7.26 + +## version 1.1.2 @2021.7.15 + +**Feature** + +- 通过 http 接口转发文件上传 +- 支持阿里云普通上传 @2021.7.6 +- 更新日志模块为 zerolog @2021.7.7 +- 支持华为云普通上传 @2021.7.7 +- 所有配置信息从配置文件中获取 @2021.7.8 +- upload接口 OssType 字段可选填, 由配置文件中同APPID的最后一个Oss.OssType决定 @1.1.2 2021.7.15 + +## version 1.0.0 @2021.6.30 + +**Feature** + +支持阿里云和华为云 oss 临时鉴权 + +## example x.x.x @yy.mm.dd + +**Feature** + +**Bug Fixes** + +**Improvement** + +**Breaking Change** diff --git a/service/oss/Makefile b/service/oss/Makefile new file mode 100644 index 0000000..1f36c45 --- /dev/null +++ b/service/oss/Makefile @@ -0,0 +1,51 @@ +VERSION := $(shell echo $(shell cat cmd/main.go | grep "projectVersion =" | cut -d '=' -f2)) +APP_NAME := oss +BUILD_DIR := build +APP := ${BUILD_DIR}/${APP_NAME}_v${VERSION} +PKG_NAME := ${APP_NAME}_v${VERSION} +PKG := ${PKG_NAME}.tar.gz + +.PHONY: clean build pkg + +main_path = "main" +go_version = $(shell go version | awk '{ print $3 }') +build_time = $(shell date "+%Y-%m-%d %H:%M:%S %Z") +git_commit = $(shell git rev-parse --short=10 HEAD) +flags := -ldflags "-X '${main_path}.goVersion=${go_version}' \ +-X '${main_path}.buildTime=${build_time}' \ +-X '${main_path}.gitCommit=${git_commit}' \ +-X 'google.golang.org/protobuf/reflect/protoregistry.conflictPolicy=warn'" + +clean: ## Remove previous build + @rm -rf ${BUILD_DIR} + @go clean + +swag: + @echo '┌ start gen swag' + @swag init -g server/http/http.go + @echo '└ end gen swag' + +build: swag + @echo '┌ start build $(APP)' + @GOOS=linux GOARCH=amd64 GO111MODULE=on GOPROXY=https://goproxy.cn,direct GOSUMDB="sum.golang.google.cn" go build -v $(flags) -o $(APP) cmd/main.go + @echo '└ end build $(APP)' + +pkg: build ## Package + mkdir -p ${PKG_NAME}/bin + mkdir -p ${PKG_NAME}/etc + cp ${APP} ${PKG_NAME}/bin/ + cp etc/* ${PKG_NAME}/etc/ + tar zvcf ${PKG} ${PKG_NAME} + rm -rf ${PKG_NAME} + +run: swag + @echo '┌ start run $(APP)' + @go run cmd/main.go -conf config/oss.toml + @echo '└ end run $(APP)' + +REMOTE_BIN_PATH := /opt/dtalk/srv/app/bin +upload: build + @echo '┌ start upload $(APP)' + @rsync -r $(APP) 107:$(REMOTE_BIN_PATH) + @ssh 107 "cd $(REMOTE_BIN_PATH) && chmod 777 $(PKG_NAME) && ln -sf $(PKG_NAME) $(APP_NAME) && supervisorctl restart $(APP_NAME)" + @echo '┌ start upload $(APP)' \ No newline at end of file diff --git a/service/oss/cmd/main.go b/service/oss/cmd/main.go new file mode 100644 index 0000000..b97ac2b --- /dev/null +++ b/service/oss/cmd/main.go @@ -0,0 +1,79 @@ +package main + +import ( + "context" + "flag" + "fmt" + "os" + "os/signal" + "syscall" + "time" + + "gitlab.33.cn/chat/dtalk/pkg/logger" + "gitlab.33.cn/chat/dtalk/service/oss/config" + "gitlab.33.cn/chat/dtalk/service/oss/server/http" + "gitlab.33.cn/chat/dtalk/service/oss/service" +) + +const srvName = "oss" + +var ( + // projectVersion 项目版本 + projectVersion = "1.5.1" + // goVersion go版本 + goVersion = "" + // gitCommit git提交commit id + gitCommit = "" + // buildTime 编译时间 + buildTime = "" + + isShowVersion = flag.Bool("v", false, "show project version") +) + +// showVersion 显示项目版本信息 +func showVersion(isShow bool) { + if isShow { + fmt.Printf("Project: %s\n", srvName) + fmt.Printf(" Version: %s\n", projectVersion) + fmt.Printf(" Go Version: %s\n", goVersion) + fmt.Printf(" Git Commit: %s\n", gitCommit) + fmt.Printf(" Build Time: %s\n", buildTime) + os.Exit(0) + } +} + +func main() { + flag.Parse() + showVersion(*isShowVersion) + + if err := config.Init(); err != nil { + panic(err) + } + log := logger.New(config.Conf.Env, srvName) + log.Info().Interface("config info", config.Conf).Msg("") + // service init + svc := service.New(config.Conf) + httpSrv := http.Init(svc) + // init signal + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT) + for { + s := <-c + log.Info().Str("signal", s.String()).Msg("service get a signal") + switch s { + case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT: + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := httpSrv.Shutdown(ctx); err != nil { + log.Error().Err(err).Msg("server shutdown") + } + time.Sleep(time.Second * 2) + log.Info().Str("srvName", srvName).Msg("server exit") + return + case syscall.SIGHUP: + // TODO reload + default: + return + } + } +} diff --git a/service/oss/config/README.md b/service/oss/config/README.md new file mode 100644 index 0000000..45626d4 --- /dev/null +++ b/service/oss/config/README.md @@ -0,0 +1 @@ +**upload 接口中的 ossType 由 oss.toml 中 appId 相同的最后一个 Oss 中的 OssType 决定** diff --git a/service/oss/config/config.go b/service/oss/config/config.go new file mode 100644 index 0000000..eb6b289 --- /dev/null +++ b/service/oss/config/config.go @@ -0,0 +1,112 @@ +package config + +import ( + "flag" + "github.com/BurntSushi/toml" + "github.com/pkg/errors" + xtime "gitlab.33.cn/chat/dtalk/pkg/time" + "time" +) + +var ( + confPath string + + Conf *Config +) + +func init() { + flag.StringVar(&confPath, "conf", "oss.toml", "default config path.") +} + +// Init init config. +func Init() (err error) { + Conf = Default() + _, err = toml.DecodeFile(confPath, &Conf) + err = check() + return +} + +func Default() *Config { + return &Config{ + Env: "debug", + Server: &HttpServer{ + Addr: "0.0.0.0:18005", + }, + Redis: &Redis{ + Network: "tcp", + Addr: "127.0.0.1:6379", + Auth: "", + Active: 60000, + Idle: 1024, + DialTimeout: xtime.Duration(200 * time.Millisecond), + ReadTimeout: xtime.Duration(500 * time.Millisecond), + WriteTimeout: xtime.Duration(500 * time.Millisecond), + IdleTimeout: xtime.Duration(120 * time.Second), + Expire: xtime.Duration(30 * time.Minute), + }, + } +} + +func check() error { + // appId+ossType 唯一 + ossMap := make(map[string]string) + for _, oss := range Conf.Oss { + key := oss.AppId + "-" + oss.OssType + if _, ok := ossMap[key]; ok { + return errors.New("有重复的 AppId 和 OssType") + } + ossMap[key] = key + } + return nil +} + +// Config config. +type Config struct { + Env string + Server *HttpServer + Redis *Redis + Oss []*Oss +} + +type HttpServer struct { + Addr string +} + +// MySQL . +type MySQL struct { + Host string + Port int32 + User string + Pwd string + Db string +} + +// Redis . +type Redis struct { + Network string + Addr string + Auth string + Active int + Idle int + DialTimeout xtime.Duration + ReadTimeout xtime.Duration + WriteTimeout xtime.Duration + IdleTimeout xtime.Duration + Expire xtime.Duration +} + +type Oss struct { + AppId string + OssType string + // RegionId 决定获得 tempKey 的速度快不快 + RegionId string + AccessKeyId string + AccessKeySecret string + Role string + Policy string + DurationSeconds int + Bucket string + // EndPoint 资源终端 + EndPoint string + PublicUrl string +} diff --git a/service/oss/config/oss.toml b/service/oss/config/oss.toml new file mode 100644 index 0000000..f94e424 --- /dev/null +++ b/service/oss/config/oss.toml @@ -0,0 +1,29 @@ +Env = "debug" + +[server] +addr="0.0.0.0:18005" + +[redis] +network="tcp" +addr="127.0.0.1:6379" +auth="" +active=60000 +idle=1024 +dialTimeout="200ms" +readTimeout="500ms" +writeTimeout="500ms" +idleTimeout="120s" +expire="30m" + +[[Oss]] +AppId = "dtalk" +OssType = "minio" +RegionId = "" +AccessKeyId = "XYI4T3QIT8YQQLRHA0YV" +AccessKeySecret = "FqgvB3CzsBK5xwEphEC6i4Y6dTkWAjyfQ9TS1kLZ" +Role = "" +Policy = "" +DurationSeconds = 3600 +Bucket = "dtalk-test" +EndPoint = "127.0.0.1:9000" +PublicUrl = "http://127.0.0.1:9000" \ No newline at end of file diff --git a/service/oss/dao/dao.go b/service/oss/dao/dao.go new file mode 100644 index 0000000..96555c7 --- /dev/null +++ b/service/oss/dao/dao.go @@ -0,0 +1,44 @@ +package dao + +import ( + "time" + + _ "github.com/go-sql-driver/mysql" + "github.com/gomodule/redigo/redis" + "github.com/rs/zerolog" + "gitlab.33.cn/chat/dtalk/pkg/logger" + conf "gitlab.33.cn/chat/dtalk/service/oss/config" +) + +type Dao struct { + log zerolog.Logger + redis *redis.Pool +} + +func New(c *conf.Config) *Dao { + d := &Dao{ + log: logger.New(c.Env, "oss/dao"), + redis: newRedis(c.Redis), + } + return d +} + +func newRedis(c *conf.Redis) *redis.Pool { + return &redis.Pool{ + MaxIdle: c.Idle, + MaxActive: c.Active, + IdleTimeout: time.Duration(c.IdleTimeout), + Dial: func() (redis.Conn, error) { + conn, err := redis.Dial(c.Network, c.Addr, + redis.DialConnectTimeout(time.Duration(c.DialTimeout)), + redis.DialReadTimeout(time.Duration(c.ReadTimeout)), + redis.DialWriteTimeout(time.Duration(c.WriteTimeout)), + redis.DialPassword(c.Auth), + ) + if err != nil { + return nil, err + } + return conn, nil + }, + } +} diff --git a/service/oss/dao/dao_test.go b/service/oss/dao/dao_test.go new file mode 100644 index 0000000..e206a16 --- /dev/null +++ b/service/oss/dao/dao_test.go @@ -0,0 +1,53 @@ +package dao + +import ( + "os" + "testing" + "time" + + "github.com/gomodule/redigo/redis" + "github.com/rs/zerolog" + zlog "github.com/rs/zerolog/log" + xtime "gitlab.33.cn/chat/dtalk/pkg/time" + "gitlab.33.cn/chat/dtalk/service/oss/config" +) + +var ( + testLog zerolog.Logger + testRedis *redis.Pool +) + +func TestMain(m *testing.M) { + testLog = zlog.Logger + c := &config.Redis{ + Network: "tcp", + Addr: "127.0.0.1:6379", + Auth: "", + Active: 60000, + Idle: 1024, + DialTimeout: xtime.Duration(200 * time.Millisecond), + ReadTimeout: xtime.Duration(500 * time.Millisecond), + WriteTimeout: xtime.Duration(500 * time.Millisecond), + IdleTimeout: xtime.Duration(120 * time.Second), + Expire: xtime.Duration(30 * time.Minute), + } + + testRedis = &redis.Pool{ + MaxIdle: c.Idle, + MaxActive: c.Active, + IdleTimeout: time.Duration(c.IdleTimeout), + Dial: func() (redis.Conn, error) { + conn, err := redis.Dial(c.Network, c.Addr, + redis.DialConnectTimeout(time.Duration(c.DialTimeout)), + redis.DialReadTimeout(time.Duration(c.ReadTimeout)), + redis.DialWriteTimeout(time.Duration(c.WriteTimeout)), + redis.DialPassword(c.Auth), + ) + if err != nil { + return nil, err + } + return conn, nil + }, + } + os.Exit(m.Run()) +} diff --git a/service/oss/dao/redis.go b/service/oss/dao/redis.go new file mode 100644 index 0000000..73d21fb --- /dev/null +++ b/service/oss/dao/redis.go @@ -0,0 +1,69 @@ +package dao + +import ( + "encoding/json" + "fmt" + + "github.com/gomodule/redigo/redis" + "gitlab.33.cn/chat/dtalk/pkg/oss" + "gitlab.33.cn/chat/dtalk/service/oss/model" +) + +const ( + _prefixOssConfigCache = "oss-config:%v-%v" +) + +func keyOssConfig(app, ossType string) string { + return fmt.Sprintf(_prefixOssConfigCache, app, ossType) +} + +//key:name; val:json +func (d *Dao) SaveAssumeRole(appId, ossType string, cfg *oss.Config, data *oss.AssumeRoleResp) error { + if cfg == nil { + return model.ErrNilPoint + } + val, err := json.Marshal(data) + if err != nil { + return err + } + key := keyOssConfig(appId, ossType) + conn := d.redis.Get() + defer conn.Close() + if err := conn.Send("SET", key, val); err != nil { + d.log.Error().Err(err).Str("key", key).Interface("val", val).Msg("conn.Send(SET)") + return err + } + if err := conn.Send("EXPIRE", key, cfg.DurationSeconds); err != nil { + d.log.Error().Err(err).Str("key", key).Interface("val", val).Msg("conn.Send(EXPIRE)") + return err + } + if err := conn.Flush(); err != nil { + d.log.Error().Err(err).Msg("conn.Flush()") + return err + } + if _, err := conn.Receive(); err != nil { + d.log.Error().Err(err).Msg("conn.Receive()") + return err + } + return nil +} + +func (d *Dao) GetAssumeRole(appId, ossType string, cfg *oss.Config) (*oss.AssumeRoleResp, error) { + if cfg == nil { + return nil, model.ErrNilPoint + } + key := keyOssConfig(appId, ossType) + conn := d.redis.Get() + defer conn.Close() + reply, err := redis.String(conn.Do("GET", key)) + if err != nil { + d.log.Error().Err(err).Str("key", key).Msg("conn.DO(GET)") + return nil, err + } + var data oss.AssumeRoleResp + err = json.Unmarshal([]byte(reply), &data) + if err != nil { + return nil, err + } + return &data, nil +} diff --git a/service/oss/dao/redis_test.go b/service/oss/dao/redis_test.go new file mode 100644 index 0000000..5465a7e --- /dev/null +++ b/service/oss/dao/redis_test.go @@ -0,0 +1,127 @@ +package dao + +import ( + "testing" + + "github.com/gomodule/redigo/redis" + "github.com/jinzhu/gorm" + "github.com/rs/zerolog" + "gitlab.33.cn/chat/dtalk/pkg/oss" +) + +func TestDao_SaveAssumeRole(t *testing.T) { + type fields struct { + log zerolog.Logger + db *gorm.DB + redis *redis.Pool + } + type args struct { + appId string + ossType string + cfg *oss.Config + data *oss.AssumeRoleResp + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "test add assumeRole", + fields: fields{ + log: testLog, + redis: testRedis, + }, + args: args{ + appId: "test", + ossType: "aliyun", + cfg: &oss.Config{ + EndPoint: "", + AccessKeyId: "", + AccessKeySecret: "", + Role: "", + Policy: "", + DurationSeconds: 20, + }, + data: &oss.AssumeRoleResp{ + RequestId: "123", + Credentials: oss.Credentials{}, + AssumedRoleUser: oss.AssumedRoleUser{}, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + d := &Dao{ + log: tt.fields.log, + redis: tt.fields.redis, + } + if err := d.SaveAssumeRole(tt.args.appId, tt.args.ossType, tt.args.cfg, tt.args.data); (err != nil) != tt.wantErr { + t.Errorf("SaveAssumeRole() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestDao_GetAssumeRole(t *testing.T) { + type fields struct { + log zerolog.Logger + db *gorm.DB + redis *redis.Pool + } + type args struct { + appId string + ossType string + cfg *oss.Config + data *oss.AssumeRoleResp + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "test add assumeRole", + fields: fields{ + log: testLog, + redis: testRedis, + }, + args: args{ + appId: "test", + ossType: "aliyun", + cfg: &oss.Config{ + EndPoint: "", + AccessKeyId: "", + AccessKeySecret: "", + Role: "", + Policy: "", + DurationSeconds: 20, + }, + data: &oss.AssumeRoleResp{ + RequestId: "123", + Credentials: oss.Credentials{}, + AssumedRoleUser: oss.AssumedRoleUser{}, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + d := &Dao{ + log: tt.fields.log, + redis: tt.fields.redis, + } + got, err := d.GetAssumeRole(tt.args.appId, tt.args.ossType, tt.args.cfg) + if (err != nil) != tt.wantErr { + t.Errorf("GetAssumeRole() error = %v, wantErr %v", err, tt.wantErr) + return + } + t.Log(got) + }) + } +} diff --git a/service/oss/docs/docs.go b/service/oss/docs/docs.go new file mode 100644 index 0000000..2b7be53 --- /dev/null +++ b/service/oss/docs/docs.go @@ -0,0 +1,621 @@ +// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT +// This file was generated by swaggo/swag + +package docs + +import ( + "bytes" + "encoding/json" + "strings" + + "github.com/alecthomas/template" + "github.com/swaggo/swag" +) + +var doc = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{.Description}}", + "title": "{{.Title}}", + "contact": {}, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/abort-multipart-upload": { + "post": { + "consumes": [ + "application/json" + ], + "tags": [ + "oss 分段上传" + ], + "summary": "取消分段上传任务", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/model.AbortMultipartUploadRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/model.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.AbortMultipartUploadResponse" + } + } + } + ] + } + } + } + } + }, + "/complete-multipart-upload": { + "post": { + "consumes": [ + "application/json" + ], + "tags": [ + "oss 分段上传" + ], + "summary": "合并段", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/model.CompleteMultipartUploadRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/model.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.CompleteMultipartUploadResponse" + } + } + } + ] + } + } + } + } + }, + "/get-host": { + "post": { + "consumes": [ + "application/json" + ], + "tags": [ + "oss" + ], + "summary": "获得 oss Host", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/model.GetHostReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/model.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.GetHostResp" + } + } + } + ] + } + } + } + } + }, + "/init-multipart-upload": { + "post": { + "consumes": [ + "application/json" + ], + "tags": [ + "oss 分段上传" + ], + "summary": "初始化分段上传任务", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/model.InitMultipartUploadRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/model.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.InitMultipartUploadResponse" + } + } + } + ] + } + } + } + } + }, + "/upload": { + "post": { + "consumes": [ + "multipart/form-data" + ], + "tags": [ + "oss 普通上传" + ], + "summary": "上传文件", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "type": "file", + "description": "file", + "name": "file", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "应用 ID", + "name": "appId", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "文件名(包含路径)", + "name": "key", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "云服务商, 可不填, 会选择默认服务商, 目前可选huaweiyun,aliyun,minio(不支持临时角色)", + "name": "ossType", + "in": "formData" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/model.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.UploadResponse" + } + } + } + ] + } + } + } + } + }, + "/upload-part": { + "post": { + "consumes": [ + "multipart/form-data" + ], + "tags": [ + "oss 分段上传" + ], + "summary": "上传段", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "type": "file", + "description": "aliyun, huaweiyun 除了最后一段以外,其他段的大小范围是100KB~5GB;最后段大小范围是0~5GB。minio 除了最后一段以外,其他段的大小范围是5MB~5GB;最后段大小范围是0~5GB。", + "name": "file", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "应用 ID", + "name": "appId", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "文件名(包含路径)", + "name": "key", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "云服务商, 可不填, 会选择默认服务商, 目前可选huaweiyun,aliyun,minio(不支持临时角色)", + "name": "ossType", + "in": "formData" + }, + { + "type": "integer", + "description": "分段序号, 范围是1~10000", + "name": "partNumber", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "分段上传任务全局唯一标识", + "name": "uploadId", + "in": "formData", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/model.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.UploadPartResponse" + } + } + } + ] + } + } + } + } + } + }, + "definitions": { + "model.AbortMultipartUploadRequest": { + "type": "object", + "required": [ + "appId", + "key", + "uploadId" + ], + "properties": { + "appId": { + "description": "应用 ID", + "type": "string" + }, + "key": { + "description": "文件名(包含路径)", + "type": "string" + }, + "ossType": { + "description": "云服务商, 可不填, 会选择默认服务商, 目前可选huaweiyun,aliyun,minio(不支持临时角色)", + "type": "string" + }, + "uploadId": { + "description": "分段上传任务全局唯一标识", + "type": "string" + } + } + }, + "model.AbortMultipartUploadResponse": { + "type": "object" + }, + "model.CompleteMultipartUploadRequest": { + "type": "object", + "required": [ + "appId", + "key", + "parts", + "uploadId" + ], + "properties": { + "appId": { + "description": "应用 ID", + "type": "string" + }, + "key": { + "description": "文件名(包含路径)", + "type": "string" + }, + "ossType": { + "description": "云服务商, 可不填, 会选择默认服务商, 目前可选huaweiyun,aliyun,minio(不支持临时角色)", + "type": "string" + }, + "parts": { + "type": "array", + "items": { + "$ref": "#/definitions/model.Part" + } + }, + "uploadId": { + "description": "分段上传任务全局唯一标识", + "type": "string" + } + } + }, + "model.CompleteMultipartUploadResponse": { + "type": "object", + "properties": { + "uri": { + "type": "string" + }, + "url": { + "description": "资源链接", + "type": "string" + } + } + }, + "model.GeneralResponse": { + "type": "object", + "properties": { + "data": { + "type": "object" + }, + "message": { + "type": "integer" + }, + "result": { + "type": "integer" + } + } + }, + "model.GetHostReq": { + "type": "object", + "required": [ + "appId" + ], + "properties": { + "appId": { + "description": "应用 ID", + "type": "string" + }, + "ossType": { + "description": "云服务商, 可不填, 会选择默认服务商, 目前可选huaweiyun,aliyun,minio(不支持临时角色)", + "type": "string" + } + } + }, + "model.GetHostResp": { + "type": "object", + "properties": { + "host": { + "type": "string" + } + } + }, + "model.InitMultipartUploadRequest": { + "type": "object", + "required": [ + "appId", + "key" + ], + "properties": { + "appId": { + "description": "应用 ID", + "type": "string" + }, + "key": { + "description": "文件名(包含路径)", + "type": "string" + }, + "ossType": { + "description": "云服务商, 可不填, 会选择默认服务商, 目前可选huaweiyun,aliyun,minio(不支持临时角色)", + "type": "string" + } + } + }, + "model.InitMultipartUploadResponse": { + "type": "object", + "properties": { + "key": { + "description": "文件名(包含路径)", + "type": "string" + }, + "uploadId": { + "description": "分段上传任务全局唯一标识", + "type": "string" + } + } + }, + "model.Part": { + "type": "object", + "required": [ + "ETag", + "partNumber" + ], + "properties": { + "ETag": { + "description": "段数据的MD5值", + "type": "string" + }, + "partNumber": { + "description": "分段序号, 范围是1~10000", + "type": "integer" + } + } + }, + "model.UploadPartResponse": { + "type": "object", + "required": [ + "ETag", + "partNumber" + ], + "properties": { + "ETag": { + "description": "段数据的MD5值", + "type": "string" + }, + "key": { + "description": "文件名(包含路径)", + "type": "string" + }, + "partNumber": { + "description": "分段序号, 范围是1~10000", + "type": "integer" + }, + "uploadId": { + "description": "分段上传任务全局唯一标识", + "type": "string" + } + } + }, + "model.UploadResponse": { + "type": "object", + "properties": { + "uri": { + "type": "string" + }, + "url": { + "description": "资源链接", + "type": "string" + } + } + } + } +}` + +type swaggerInfo struct { + Version string + Host string + BasePath string + Schemes []string + Title string + Description string +} + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = swaggerInfo{ + Version: "1.0", + Host: "127.0.0.1:18005", + BasePath: "", + Schemes: []string{}, + Title: "云存储服务接口", + Description: "", +} + +type s struct{} + +func (s *s) ReadDoc() string { + sInfo := SwaggerInfo + sInfo.Description = strings.Replace(sInfo.Description, "\n", "\\n", -1) + + t, err := template.New("swagger_info").Funcs(template.FuncMap{ + "marshal": func(v interface{}) string { + a, _ := json.Marshal(v) + return string(a) + }, + }).Parse(doc) + if err != nil { + return doc + } + + var tpl bytes.Buffer + if err := t.Execute(&tpl, sInfo); err != nil { + return doc + } + + return tpl.String() +} + +func init() { + swag.Register(swag.Name, &s{}) +} diff --git a/service/oss/docs/swagger.json b/service/oss/docs/swagger.json new file mode 100644 index 0000000..21e75f0 --- /dev/null +++ b/service/oss/docs/swagger.json @@ -0,0 +1,557 @@ +{ + "swagger": "2.0", + "info": { + "title": "云存储服务接口", + "contact": {}, + "version": "1.0" + }, + "host": "127.0.0.1:18005", + "paths": { + "/abort-multipart-upload": { + "post": { + "consumes": [ + "application/json" + ], + "tags": [ + "oss 分段上传" + ], + "summary": "取消分段上传任务", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/model.AbortMultipartUploadRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/model.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.AbortMultipartUploadResponse" + } + } + } + ] + } + } + } + } + }, + "/complete-multipart-upload": { + "post": { + "consumes": [ + "application/json" + ], + "tags": [ + "oss 分段上传" + ], + "summary": "合并段", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/model.CompleteMultipartUploadRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/model.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.CompleteMultipartUploadResponse" + } + } + } + ] + } + } + } + } + }, + "/get-host": { + "post": { + "consumes": [ + "application/json" + ], + "tags": [ + "oss" + ], + "summary": "获得 oss Host", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/model.GetHostReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/model.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.GetHostResp" + } + } + } + ] + } + } + } + } + }, + "/init-multipart-upload": { + "post": { + "consumes": [ + "application/json" + ], + "tags": [ + "oss 分段上传" + ], + "summary": "初始化分段上传任务", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/model.InitMultipartUploadRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/model.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.InitMultipartUploadResponse" + } + } + } + ] + } + } + } + } + }, + "/upload": { + "post": { + "consumes": [ + "multipart/form-data" + ], + "tags": [ + "oss 普通上传" + ], + "summary": "上传文件", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "type": "file", + "description": "file", + "name": "file", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "应用 ID", + "name": "appId", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "文件名(包含路径)", + "name": "key", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "云服务商, 可不填, 会选择默认服务商, 目前可选huaweiyun,aliyun,minio(不支持临时角色)", + "name": "ossType", + "in": "formData" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/model.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.UploadResponse" + } + } + } + ] + } + } + } + } + }, + "/upload-part": { + "post": { + "consumes": [ + "multipart/form-data" + ], + "tags": [ + "oss 分段上传" + ], + "summary": "上传段", + "parameters": [ + { + "type": "string", + "description": "MOCK", + "name": "FZM-SIGNATURE", + "in": "header", + "required": true + }, + { + "type": "file", + "description": "aliyun, huaweiyun 除了最后一段以外,其他段的大小范围是100KB~5GB;最后段大小范围是0~5GB。minio 除了最后一段以外,其他段的大小范围是5MB~5GB;最后段大小范围是0~5GB。", + "name": "file", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "应用 ID", + "name": "appId", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "文件名(包含路径)", + "name": "key", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "云服务商, 可不填, 会选择默认服务商, 目前可选huaweiyun,aliyun,minio(不支持临时角色)", + "name": "ossType", + "in": "formData" + }, + { + "type": "integer", + "description": "分段序号, 范围是1~10000", + "name": "partNumber", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "分段上传任务全局唯一标识", + "name": "uploadId", + "in": "formData", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/model.GeneralResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.UploadPartResponse" + } + } + } + ] + } + } + } + } + } + }, + "definitions": { + "model.AbortMultipartUploadRequest": { + "type": "object", + "required": [ + "appId", + "key", + "uploadId" + ], + "properties": { + "appId": { + "description": "应用 ID", + "type": "string" + }, + "key": { + "description": "文件名(包含路径)", + "type": "string" + }, + "ossType": { + "description": "云服务商, 可不填, 会选择默认服务商, 目前可选huaweiyun,aliyun,minio(不支持临时角色)", + "type": "string" + }, + "uploadId": { + "description": "分段上传任务全局唯一标识", + "type": "string" + } + } + }, + "model.AbortMultipartUploadResponse": { + "type": "object" + }, + "model.CompleteMultipartUploadRequest": { + "type": "object", + "required": [ + "appId", + "key", + "parts", + "uploadId" + ], + "properties": { + "appId": { + "description": "应用 ID", + "type": "string" + }, + "key": { + "description": "文件名(包含路径)", + "type": "string" + }, + "ossType": { + "description": "云服务商, 可不填, 会选择默认服务商, 目前可选huaweiyun,aliyun,minio(不支持临时角色)", + "type": "string" + }, + "parts": { + "type": "array", + "items": { + "$ref": "#/definitions/model.Part" + } + }, + "uploadId": { + "description": "分段上传任务全局唯一标识", + "type": "string" + } + } + }, + "model.CompleteMultipartUploadResponse": { + "type": "object", + "properties": { + "uri": { + "type": "string" + }, + "url": { + "description": "资源链接", + "type": "string" + } + } + }, + "model.GeneralResponse": { + "type": "object", + "properties": { + "data": { + "type": "object" + }, + "message": { + "type": "integer" + }, + "result": { + "type": "integer" + } + } + }, + "model.GetHostReq": { + "type": "object", + "required": [ + "appId" + ], + "properties": { + "appId": { + "description": "应用 ID", + "type": "string" + }, + "ossType": { + "description": "云服务商, 可不填, 会选择默认服务商, 目前可选huaweiyun,aliyun,minio(不支持临时角色)", + "type": "string" + } + } + }, + "model.GetHostResp": { + "type": "object", + "properties": { + "host": { + "type": "string" + } + } + }, + "model.InitMultipartUploadRequest": { + "type": "object", + "required": [ + "appId", + "key" + ], + "properties": { + "appId": { + "description": "应用 ID", + "type": "string" + }, + "key": { + "description": "文件名(包含路径)", + "type": "string" + }, + "ossType": { + "description": "云服务商, 可不填, 会选择默认服务商, 目前可选huaweiyun,aliyun,minio(不支持临时角色)", + "type": "string" + } + } + }, + "model.InitMultipartUploadResponse": { + "type": "object", + "properties": { + "key": { + "description": "文件名(包含路径)", + "type": "string" + }, + "uploadId": { + "description": "分段上传任务全局唯一标识", + "type": "string" + } + } + }, + "model.Part": { + "type": "object", + "required": [ + "ETag", + "partNumber" + ], + "properties": { + "ETag": { + "description": "段数据的MD5值", + "type": "string" + }, + "partNumber": { + "description": "分段序号, 范围是1~10000", + "type": "integer" + } + } + }, + "model.UploadPartResponse": { + "type": "object", + "required": [ + "ETag", + "partNumber" + ], + "properties": { + "ETag": { + "description": "段数据的MD5值", + "type": "string" + }, + "key": { + "description": "文件名(包含路径)", + "type": "string" + }, + "partNumber": { + "description": "分段序号, 范围是1~10000", + "type": "integer" + }, + "uploadId": { + "description": "分段上传任务全局唯一标识", + "type": "string" + } + } + }, + "model.UploadResponse": { + "type": "object", + "properties": { + "uri": { + "type": "string" + }, + "url": { + "description": "资源链接", + "type": "string" + } + } + } + } +} \ No newline at end of file diff --git a/service/oss/docs/swagger.yaml b/service/oss/docs/swagger.yaml new file mode 100644 index 0000000..394ee9e --- /dev/null +++ b/service/oss/docs/swagger.yaml @@ -0,0 +1,359 @@ +definitions: + model.AbortMultipartUploadRequest: + properties: + appId: + description: 应用 ID + type: string + key: + description: 文件名(包含路径) + type: string + ossType: + description: 云服务商, 可不填, 会选择默认服务商, 目前可选huaweiyun,aliyun,minio(不支持临时角色) + type: string + uploadId: + description: 分段上传任务全局唯一标识 + type: string + required: + - appId + - key + - uploadId + type: object + model.AbortMultipartUploadResponse: + type: object + model.CompleteMultipartUploadRequest: + properties: + appId: + description: 应用 ID + type: string + key: + description: 文件名(包含路径) + type: string + ossType: + description: 云服务商, 可不填, 会选择默认服务商, 目前可选huaweiyun,aliyun,minio(不支持临时角色) + type: string + parts: + items: + $ref: '#/definitions/model.Part' + type: array + uploadId: + description: 分段上传任务全局唯一标识 + type: string + required: + - appId + - key + - parts + - uploadId + type: object + model.CompleteMultipartUploadResponse: + properties: + uri: + type: string + url: + description: 资源链接 + type: string + type: object + model.GeneralResponse: + properties: + data: + type: object + message: + type: integer + result: + type: integer + type: object + model.GetHostReq: + properties: + appId: + description: 应用 ID + type: string + ossType: + description: 云服务商, 可不填, 会选择默认服务商, 目前可选huaweiyun,aliyun,minio(不支持临时角色) + type: string + required: + - appId + type: object + model.GetHostResp: + properties: + host: + type: string + type: object + model.InitMultipartUploadRequest: + properties: + appId: + description: 应用 ID + type: string + key: + description: 文件名(包含路径) + type: string + ossType: + description: 云服务商, 可不填, 会选择默认服务商, 目前可选huaweiyun,aliyun,minio(不支持临时角色) + type: string + required: + - appId + - key + type: object + model.InitMultipartUploadResponse: + properties: + key: + description: 文件名(包含路径) + type: string + uploadId: + description: 分段上传任务全局唯一标识 + type: string + type: object + model.Part: + properties: + ETag: + description: 段数据的MD5值 + type: string + partNumber: + description: 分段序号, 范围是1~10000 + type: integer + required: + - ETag + - partNumber + type: object + model.UploadPartResponse: + properties: + ETag: + description: 段数据的MD5值 + type: string + key: + description: 文件名(包含路径) + type: string + partNumber: + description: 分段序号, 范围是1~10000 + type: integer + uploadId: + description: 分段上传任务全局唯一标识 + type: string + required: + - ETag + - partNumber + type: object + model.UploadResponse: + properties: + uri: + type: string + url: + description: 资源链接 + type: string + type: object +host: 127.0.0.1:18005 +info: + contact: {} + title: 云存储服务接口 + version: "1.0" +paths: + /abort-multipart-upload: + post: + consumes: + - application/json + parameters: + - description: MOCK + in: header + name: FZM-SIGNATURE + required: true + type: string + - description: body + in: body + name: data + required: true + schema: + $ref: '#/definitions/model.AbortMultipartUploadRequest' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/model.GeneralResponse' + - properties: + data: + $ref: '#/definitions/model.AbortMultipartUploadResponse' + type: object + summary: 取消分段上传任务 + tags: + - oss 分段上传 + /complete-multipart-upload: + post: + consumes: + - application/json + parameters: + - description: MOCK + in: header + name: FZM-SIGNATURE + required: true + type: string + - description: body + in: body + name: data + required: true + schema: + $ref: '#/definitions/model.CompleteMultipartUploadRequest' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/model.GeneralResponse' + - properties: + data: + $ref: '#/definitions/model.CompleteMultipartUploadResponse' + type: object + summary: 合并段 + tags: + - oss 分段上传 + /get-host: + post: + consumes: + - application/json + parameters: + - description: MOCK + in: header + name: FZM-SIGNATURE + required: true + type: string + - description: body + in: body + name: data + required: true + schema: + $ref: '#/definitions/model.GetHostReq' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/model.GeneralResponse' + - properties: + data: + $ref: '#/definitions/model.GetHostResp' + type: object + summary: 获得 oss Host + tags: + - oss + /init-multipart-upload: + post: + consumes: + - application/json + parameters: + - description: MOCK + in: header + name: FZM-SIGNATURE + required: true + type: string + - description: body + in: body + name: data + required: true + schema: + $ref: '#/definitions/model.InitMultipartUploadRequest' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/model.GeneralResponse' + - properties: + data: + $ref: '#/definitions/model.InitMultipartUploadResponse' + type: object + summary: 初始化分段上传任务 + tags: + - oss 分段上传 + /upload: + post: + consumes: + - multipart/form-data + parameters: + - description: MOCK + in: header + name: FZM-SIGNATURE + required: true + type: string + - description: file + in: formData + name: file + required: true + type: file + - description: 应用 ID + in: formData + name: appId + required: true + type: string + - description: 文件名(包含路径) + in: formData + name: key + required: true + type: string + - description: 云服务商, 可不填, 会选择默认服务商, 目前可选huaweiyun,aliyun,minio(不支持临时角色) + in: formData + name: ossType + type: string + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/model.GeneralResponse' + - properties: + data: + $ref: '#/definitions/model.UploadResponse' + type: object + summary: 上传文件 + tags: + - oss 普通上传 + /upload-part: + post: + consumes: + - multipart/form-data + parameters: + - description: MOCK + in: header + name: FZM-SIGNATURE + required: true + type: string + - description: aliyun, huaweiyun 除了最后一段以外,其他段的大小范围是100KB~5GB;最后段大小范围是0~5GB。minio + 除了最后一段以外,其他段的大小范围是5MB~5GB;最后段大小范围是0~5GB。 + in: formData + name: file + required: true + type: file + - description: 应用 ID + in: formData + name: appId + required: true + type: string + - description: 文件名(包含路径) + in: formData + name: key + required: true + type: string + - description: 云服务商, 可不填, 会选择默认服务商, 目前可选huaweiyun,aliyun,minio(不支持临时角色) + in: formData + name: ossType + type: string + - description: 分段序号, 范围是1~10000 + in: formData + name: partNumber + required: true + type: integer + - description: 分段上传任务全局唯一标识 + in: formData + name: uploadId + required: true + type: string + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/model.GeneralResponse' + - properties: + data: + $ref: '#/definitions/model.UploadPartResponse' + type: object + summary: 上传段 + tags: + - oss 分段上传 +swagger: "2.0" diff --git a/service/oss/model/const.go b/service/oss/model/const.go new file mode 100644 index 0000000..9078de6 --- /dev/null +++ b/service/oss/model/const.go @@ -0,0 +1,13 @@ +package model + +const Oss_Aliyun = "aliyun" +const Oss_Huaweiuyn = "huaweiyun" +const Oss_Minio = "minio" + +// MinPartSize - absolute minimum part size (5 MiB) below which +// a part in a multipart upload may not be uploaded. +const MinPartSize = 1024 * 1024 * 5 + +// MaxPartSize - maximum part size 5GiB for a single multipart upload +// operation. +const MaxPartSize = 1024 * 1024 * 1024 * 5 diff --git a/service/oss/model/error.go b/service/oss/model/error.go new file mode 100644 index 0000000..4ab2b81 --- /dev/null +++ b/service/oss/model/error.go @@ -0,0 +1,7 @@ +package model + +import "errors" + +var ( + ErrNilPoint = errors.New("未知参数") +) diff --git a/service/oss/model/http.go b/service/oss/model/http.go new file mode 100644 index 0000000..c67da6f --- /dev/null +++ b/service/oss/model/http.go @@ -0,0 +1,142 @@ +package model + +import ( + "io" + + "gitlab.33.cn/chat/dtalk/pkg/oss" +) + +type GeneralResponse struct { + Result int `json:"result"` + Message int `json:"message"` + Data interface{} `json:"data"` +} + +type ossBase struct { + // 应用 ID + AppId string `json:"appId" form:"appId" binding:"required"` + + // 云服务商, 可不填, 会选择默认服务商, 目前可选huaweiyun,aliyun,minio(不支持临时角色) + OssType string `json:"ossType" form:"ossType"` + + // 上传者 + PersonId string `json:"-" from:"-"` +} + +type Part struct { + // 段数据的MD5值 + ETag string `json:"ETag" form:"ETag" binding:"required"` + // 分段序号, 范围是1~10000 + PartNumber int32 `json:"partNumber" form:"partNumber" binding:"required"` +} + +func (p *Part) Convert2OssPart() oss.Part { + return oss.Part{ + ETag: p.ETag, + PartNumber: p.PartNumber, + } +} + +type UploadRequest struct { + ossBase + + // 文件名(包含路径) + Key string `json:"key" form:"key" binding:"required"` + + // 桶名 + //Bucket string `json:"bucket" form:"bucket" binding:"required"` + + // 上传文件的人 + //Person string `json:"person" form:"person" binding:"required"` + + // 文件内容 + Body io.Reader `json:"-" form:"-"` + Size int64 `json:"-" from:"-"` +} + +type UploadResponse struct { + // 资源链接 + Url string `json:"url"` + Uri string `json:"uri"` +} + +type InitMultipartUploadRequest struct { + ossBase + + // 文件名(包含路径) + Key string `json:"key" form:"key" binding:"required"` +} + +type InitMultipartUploadResponse struct { + // 分段上传任务全局唯一标识 + UploadId string `json:"uploadId"` + // 文件名(包含路径) + Key string `json:"key"` +} + +type UploadPartRequest struct { + ossBase + + // 文件名(包含路径) + Key string `json:"key" form:"key" binding:"required"` + // 分段上传任务全局唯一标识 + UploadId string `json:"uploadId" form:"uploadId" binding:"required"` + // 分段序号, 范围是1~10000 + PartNumber int32 `json:"partNumber" form:"partNumber" binding:"required"` + + // 偏移量, 可不填 + Offset int64 `json:"-" form:"-"` + + // 分段文件大小, 可不填 + PartSize int64 `json:"-" form:"-"` + + // 文件内容 + // aliyun, huaweiyun 除了最后一段以外,其他段的大小范围是100KB~5GB;最后段大小范围是0~5GB。 + // minio 除了最后一段以外,其他段的大小范围是5MB~5GB;最后段大小范围是0~5GB。 + Body io.Reader `json:"-" form:"-"` +} + +type UploadPartResponse struct { + // 分段上传任务全局唯一标识 + UploadId string `json:"uploadId"` + // 文件名(包含路径) + Key string `json:"key"` + Part +} + +type CompleteMultipartUploadRequest struct { + ossBase + + // 文件名(包含路径) + Key string `json:"key" form:"key" binding:"required"` + // 分段上传任务全局唯一标识 + UploadId string `json:"uploadId" form:"uploadId" binding:"required"` + // + Parts []Part `json:"parts" binding:"required"` +} + +type CompleteMultipartUploadResponse struct { + // 资源链接 + Url string `json:"url"` + Uri string `json:"uri"` +} + +type AbortMultipartUploadRequest struct { + ossBase + + // 文件名(包含路径) + Key string `json:"key" form:"key" binding:"required"` + // 分段上传任务全局唯一标识 + UploadId string `json:"uploadId" form:"uploadId" binding:"required"` +} + +type AbortMultipartUploadResponse struct { +} + +type GetHostReq struct { + ossBase +} + +type GetHostResp struct { + Host string `json:"host"` +} diff --git a/service/oss/model/model.go b/service/oss/model/model.go new file mode 100644 index 0000000..683064f --- /dev/null +++ b/service/oss/model/model.go @@ -0,0 +1,27 @@ +package model + +import ( + "gitlab.33.cn/chat/dtalk/pkg/oss" +) + +// App 代表一个应用, 一个 app 中存在多种 oss 存储方式 +type App struct { + DefaultOssType string + ossInv map[string]oss.Oss + AppId string +} + +func NewApp(appId string) *App { + return &App{ + ossInv: make(map[string]oss.Oss), + AppId: appId, + } +} + +func (a *App) Register(ossType string, eng oss.Oss) { + a.ossInv[ossType] = eng +} + +func (a *App) GetInvoker(ossType string) oss.Oss { + return a.ossInv[ossType] +} diff --git a/service/oss/server/http/http.go b/service/oss/server/http/http.go new file mode 100644 index 0000000..d72568c --- /dev/null +++ b/service/oss/server/http/http.go @@ -0,0 +1,77 @@ +package http + +import ( + "github.com/gin-gonic/gin" + "github.com/inconshreveable/log15" + "gitlab.33.cn/chat/dtalk/pkg/api" + "gitlab.33.cn/chat/dtalk/service/oss/service" + "net/http" + + swaggerFiles "github.com/swaggo/files" + ginSwagger "github.com/swaggo/gin-swagger" + _ "gitlab.33.cn/chat/dtalk/service/oss/docs" +) + +var ( + svc *service.Service + log = log15.New("module", "oss/http") +) + +func Init(s *service.Service) *http.Server { + addr := s.Config().Server.Addr + engine := Default() + InitService(s) + SetupEngine(engine) + + srv := &http.Server{ + Addr: addr, + Handler: engine, + } + go func() { + // service connections + if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Error("engineInner.Start() error(%v)", err) + panic(err) + } + }() + return srv +} + +// Default returns an Engine instance with the Logger and Recovery middleware already attached. +func Default() *gin.Engine { + router := gin.New() + // LoggerWithFormatter middleware will write the logs to gin.DefaultWriter + // By default gin.DefaultWriter = os.Stdout + router.Use(gin.LoggerWithFormatter(api.Chat33GinLogFormatter)) + router.Use(gin.Recovery()) + return router +} + +func InitService(s *service.Service) { + svc = s +} + +// SetupEngine +// @title 云存储服务接口 +// @version 1.0 +// @host 127.0.0.1:18005 +func SetupEngine(e *gin.Engine) *gin.Engine { + root := e.Group("/", api.RespMiddleWare()) + //获取服务器列表 + root.Use(api.AuthMiddleWare()) + { + root.POST("/get-token", GetOssToken) + root.GET("/get-token", GetOssToken) + root.POST("/get-huaweiyun-token", GetHuaweiyunOssToken) + root.GET("/get-huaweiyun-token", GetHuaweiyunOssToken) + root.POST("/upload", Upload) + root.POST("/init-multipart-upload", InitMultipartUpload) + root.POST("/upload-part", UploadPart) + root.POST("/complete-multipart-upload", CompleteMultipartUpload) + root.POST("/abort-multipart-upload", AbortMultipartUpload) + root.POST("/get-host", GetHost) + } + + e.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) + return e +} diff --git a/service/oss/server/http/oss.go b/service/oss/server/http/oss.go new file mode 100644 index 0000000..f63ec1f --- /dev/null +++ b/service/oss/server/http/oss.go @@ -0,0 +1,238 @@ +package http + +import ( + "github.com/gin-gonic/gin" + "gitlab.33.cn/chat/dtalk/pkg/api" + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/service/oss/model" +) + +func GetOssToken(c *gin.Context) { + resp, err := svc.AssumeRole("dtalk", model.Oss_Aliyun) + c.Set(api.ReqResult, resp) + c.Set(api.ReqError, err) +} + +func GetHuaweiyunOssToken(c *gin.Context) { + resp, err := svc.AssumeRole("dtalk", model.Oss_Huaweiuyn) + c.Set(api.ReqResult, resp) + c.Set(api.ReqError, err) +} + +// Upload +// @Summary 上传文件 +// @Author chy@33.cn +// @Tags oss 普通上传 +// @Accept multipart/form-data +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param file formData file true "file" +// @Param data formData model.UploadRequest true "body" +// @Success 200 {object} model.GeneralResponse{data=model.UploadResponse} +// @Router /upload [post] +func Upload(c *gin.Context) { + req := &model.UploadRequest{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + file, err := c.FormFile("file") + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + fileOpen, err := file.Open() + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + defer fileOpen.Close() + + req.Size = file.Size + req.Body = fileOpen + + if file.Size > model.MaxPartSize { + c.Set(api.ReqError, xerror.NewError(xerror.OssFileTooBig)) + return + } + + userId, ok := c.Get(api.Address) + if !ok { + c.Set(api.ReqError, xerror.NewError(xerror.SignatureInvalid)) + return + } + req.PersonId = userId.(string) + + res, err := svc.Upload(req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) +} + +// InitMultipartUpload +// @Summary 初始化分段上传任务 +// @Author chy@33.cn +// @Tags oss 分段上传 +// @Accept application/json +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body model.InitMultipartUploadRequest true "body" +// @Success 200 {object} model.GeneralResponse{data=model.InitMultipartUploadResponse} +// @Router /init-multipart-upload [post] +func InitMultipartUpload(c *gin.Context) { + req := &model.InitMultipartUploadRequest{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + userId, ok := c.Get(api.Address) + if !ok { + c.Set(api.ReqError, xerror.NewError(xerror.SignatureInvalid)) + return + } + req.PersonId = userId.(string) + + res, err := svc.InitiateMultipartUpload(req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) +} + +// UploadPart +// @Summary 上传段 +// @Author chy@33.cn +// @Tags oss 分段上传 +// @Accept multipart/form-data +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param file formData file true "aliyun, huaweiyun 除了最后一段以外,其他段的大小范围是100KB~5GB;最后段大小范围是0~5GB。minio 除了最后一段以外,其他段的大小范围是5MB~5GB;最后段大小范围是0~5GB。" +// @Param data formData model.UploadPartRequest true "body" +// @Success 200 {object} model.GeneralResponse{data=model.UploadPartResponse} +// @Router /upload-part [post] +func UploadPart(c *gin.Context) { + req := &model.UploadPartRequest{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + file, err := c.FormFile("file") + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + + fileOpen, err := file.Open() + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + defer fileOpen.Close() + + req.PartSize = file.Size + req.Body = fileOpen + + //if file.Size < model.MinPartSize { + // c.Set(api.ReqError, xerror.NewError(xerror.OssFileTooSmall)) + // return + //} + + if file.Size > model.MaxPartSize { + c.Set(api.ReqError, xerror.NewError(xerror.OssFileTooBig)) + return + } + + userId, ok := c.Get(api.Address) + if !ok { + c.Set(api.ReqError, xerror.NewError(xerror.SignatureInvalid)) + return + } + req.PersonId = userId.(string) + + res, err := svc.UploadPart(req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) +} + +// CompleteMultipartUpload +// @Summary 合并段 +// @Author chy@33.cn +// @Tags oss 分段上传 +// @Accept application/json +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body model.CompleteMultipartUploadRequest true "body" +// @Success 200 {object} model.GeneralResponse{data=model.CompleteMultipartUploadResponse} +// @Router /complete-multipart-upload [post] +func CompleteMultipartUpload(c *gin.Context) { + req := &model.CompleteMultipartUploadRequest{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + userId, ok := c.Get(api.Address) + if !ok { + c.Set(api.ReqError, xerror.NewError(xerror.SignatureInvalid)) + return + } + req.PersonId = userId.(string) + + res, err := svc.CompleteMultipartUpload(req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) +} + +// AbortMultipartUpload +// @Summary 取消分段上传任务 +// @Author chy@33.cn +// @Tags oss 分段上传 +// @Accept application/json +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body model.AbortMultipartUploadRequest true "body" +// @Success 200 {object} model.GeneralResponse{data=model.AbortMultipartUploadResponse} +// @Router /abort-multipart-upload [post] +func AbortMultipartUpload(c *gin.Context) { + req := &model.AbortMultipartUploadRequest{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + userId, ok := c.Get(api.Address) + if !ok { + c.Set(api.ReqError, xerror.NewError(xerror.SignatureInvalid)) + return + } + req.PersonId = userId.(string) + + res, err := svc.AbortMultipartUpload(req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) +} + +// GetHost +// @Summary 获得 oss Host +// @Author chy@33.cn +// @Tags oss +// @Accept application/json +// @Param FZM-SIGNATURE header string true "MOCK" +// @Param data body model.GetHostReq true "body" +// @Success 200 {object} model.GeneralResponse{data=model.GetHostResp} +// @Router /get-host [post] +func GetHost(c *gin.Context) { + req := &model.GetHostReq{} + err := c.ShouldBind(req) + if err != nil { + c.Set(api.ReqError, xerror.NewError(xerror.ParamsError).SetExtMessage(err.Error())) + return + } + userId, ok := c.Get(api.Address) + if !ok { + c.Set(api.ReqError, xerror.NewError(xerror.SignatureInvalid)) + return + } + req.PersonId = userId.(string) + + res, err := svc.GetHost(req) + c.Set(api.ReqResult, res) + c.Set(api.ReqError, err) +} diff --git a/service/oss/service/oss.go b/service/oss/service/oss.go new file mode 100644 index 0000000..6e33a6b --- /dev/null +++ b/service/oss/service/oss.go @@ -0,0 +1,250 @@ +package service + +import ( + "strings" + + xerror "gitlab.33.cn/chat/dtalk/pkg/error" + "gitlab.33.cn/chat/dtalk/pkg/oss" + "gitlab.33.cn/chat/dtalk/service/oss/model" +) + +func (s *Service) AssumeRole(appId, ossType string) (*oss.AssumeRoleResp, error) { + invoker, err := s.getInvoker(appId, ossType) + if err != nil { + return nil, err + } + + assume, err := s.load(appId, ossType, invoker) + if err == nil { + return assume, nil + } + + assume, err = invoker.AssumeRole() + if err != nil { + return nil, xerror.NewError(xerror.CodeInnerError) + } + err = s.save(appId, ossType, invoker, assume) + if err != nil { + s.log.Warn().Err(err).Interface("assume", assume).Msg("can not save assumeRole") + } + return assume, nil +} + +func (s *Service) load(appId, ossType string, invoker oss.Oss) (*oss.AssumeRoleResp, error) { + return s.dao.GetAssumeRole(appId, ossType, invoker.Config()) +} + +func (s *Service) save(appId, ossType string, invoker oss.Oss, data *oss.AssumeRoleResp) error { + return s.dao.SaveAssumeRole(appId, ossType, invoker.Config(), data) +} + +// Upload 上传文件 +func (s *Service) Upload(req *model.UploadRequest) (res *model.UploadResponse, err error) { + var fullErr error + defer func() { + if err != nil { + s.log.Error().Err(err).Err(fullErr).Interface("req", req).Str("PersonIs", req.PersonId).Msg("Upload") + } else { + s.log.Info().Interface("req", req).Interface("res", res).Str("PersonIs", req.PersonId).Msg("Upload") + } + }() + + if err := checkKey(req.Key); err != nil { + return nil, err + } + + invoker, err := s.getInvoker(req.AppId, req.OssType) + if err != nil { + return nil, err + } + + url, uri, err := invoker.Upload(req.Key, req.Body, req.Size) + if err != nil { + fullErr = err + return &model.UploadResponse{}, xerror.NewError(xerror.CodeInnerError) + } + return &model.UploadResponse{ + Url: url, + Uri: uri, + }, nil +} + +// InitiateMultipartUpload 初始化分段上传任务 +func (s *Service) InitiateMultipartUpload(req *model.InitMultipartUploadRequest) (res *model.InitMultipartUploadResponse, err error) { + var fullErr error + defer func() { + if err != nil { + s.log.Error().Err(err).Err(fullErr).Interface("req", req).Str("PersonIs", req.PersonId).Msg("InitiateMultipartUpload") + } else { + s.log.Info().Interface("req", req).Interface("res", res).Str("PersonIs", req.PersonId).Msg("InitiateMultipartUpload") + } + }() + + if err := checkKey(req.Key); err != nil { + return nil, err + } + + invoker, err := s.getInvoker(req.AppId, req.OssType) + if err != nil { + return nil, err + } + + uploadId, err := invoker.InitiateMultipartUpload(req.Key) + if err != nil { + fullErr = err + return nil, xerror.NewError(xerror.CodeInnerError) + } + return &model.InitMultipartUploadResponse{ + UploadId: uploadId, + Key: req.Key, + }, nil +} + +// UploadPart 上传段 +func (s *Service) UploadPart(req *model.UploadPartRequest) (res *model.UploadPartResponse, err error) { + var fullErr error + defer func() { + if err != nil { + s.log.Error().Err(err).Err(fullErr).Interface("req", req).Str("PersonIs", req.PersonId).Msg("UploadPart") + } else { + s.log.Info().Interface("req", req).Interface("res", res).Str("PersonIs", req.PersonId).Msg("UploadPart") + } + }() + + if err := checkKey(req.Key); err != nil { + return nil, err + } + + invoker, err := s.getInvoker(req.AppId, req.OssType) + if err != nil { + return nil, err + } + + ETag, err := invoker.UploadPart(req.Key, req.UploadId, req.Body, req.PartNumber, req.Offset, req.PartSize) + if err != nil { + fullErr = err + return nil, xerror.NewError(xerror.CodeInnerError) + } + + res = &model.UploadPartResponse{} + res.UploadId = req.UploadId + res.Key = req.Key + res.ETag = ETag + res.PartNumber = req.PartNumber + return res, nil +} + +// CompleteMultipartUpload 合并段 +func (s *Service) CompleteMultipartUpload(req *model.CompleteMultipartUploadRequest) (res *model.CompleteMultipartUploadResponse, err error) { + var fullErr error + defer func() { + if err != nil { + s.log.Error().Err(err).Err(fullErr).Interface("req", req).Str("PersonIs", req.PersonId).Msg("CompleteMultipartUpload") + } else { + s.log.Info().Interface("req", req).Interface("res", res).Str("PersonIs", req.PersonId).Msg("CompleteMultipartUpload") + } + }() + + if err := checkKey(req.Key); err != nil { + return nil, err + } + + invoker, err := s.getInvoker(req.AppId, req.OssType) + if err != nil { + return nil, err + } + + ossParts := make([]oss.Part, len(req.Parts), len(req.Parts)) + for i := range ossParts { + ossParts[i] = req.Parts[i].Convert2OssPart() + } + url, uri, err := invoker.CompleteMultipartUpload(req.Key, req.UploadId, ossParts) + if err != nil { + fullErr = err + return nil, xerror.NewError(xerror.CodeInnerError) + } + return &model.CompleteMultipartUploadResponse{ + Url: url, + Uri: uri, + }, nil +} + +// AbortMultipartUpload 取消分段上传任务 +func (s *Service) AbortMultipartUpload(req *model.AbortMultipartUploadRequest) (res *model.AbortMultipartUploadResponse, err error) { + var fullErr error + defer func() { + if err != nil { + s.log.Error().Err(err).Err(fullErr).Interface("req", req).Str("PersonIs", req.PersonId).Msg("AbortMultipartUpload") + } else { + s.log.Info().Interface("req", req).Interface("res", res).Str("PersonIs", req.PersonId).Msg("AbortMultipartUpload") + } + }() + + if err := checkKey(req.Key); err != nil { + return nil, err + } + + invoker, err := s.getInvoker(req.AppId, req.OssType) + if err != nil { + return nil, err + } + + err = invoker.AbortMultipartUpload(req.Key, req.UploadId) + if err != nil { + fullErr = err + return nil, xerror.NewError(xerror.CodeInnerError) + } + return &model.AbortMultipartUploadResponse{}, nil +} + +func (s *Service) getInvoker(appId, ossType string) (oss.Oss, error) { + a := s.GetEngine(appId) + if a == nil { + return nil, xerror.NewError(xerror.FeaturesUnSupported) + } + + // 未选择服务商, 使用默认服务商 + if ossType == "" { + ossType = a.DefaultOssType + } + invoker := a.GetInvoker(ossType) + if invoker == nil { + return nil, xerror.NewError(xerror.FeaturesUnSupported) + } + return invoker, nil +} + +func (s *Service) GetHost(req *model.GetHostReq) (res *model.GetHostResp, err error) { + var fullErr error + defer func() { + if err != nil { + s.log.Error().Err(err).Err(fullErr).Interface("req", req).Str("PersonIs", req.PersonId).Msg("GetHost") + } else { + s.log.Info().Interface("req", req).Interface("res", res).Str("PersonIs", req.PersonId).Msg("GetHost") + } + }() + + invoker, err := s.getInvoker(req.AppId, req.OssType) + if err != nil { + return nil, err + } + + host := invoker.GetHost() + return &model.GetHostResp{ + Host: host, + }, nil +} + +func checkKey(key string) error { + // key 非空 + if strings.TrimSpace(key) == "" { + return xerror.NewError(xerror.OssKeyIllegal) + } + + // key 不包含 .. + if strings.Contains(key, "..") { + return xerror.NewError(xerror.OssKeyIllegal) + } + + return nil +} diff --git a/service/oss/service/service.go b/service/oss/service/service.go new file mode 100644 index 0000000..e51508e --- /dev/null +++ b/service/oss/service/service.go @@ -0,0 +1,84 @@ +package service + +import ( + "github.com/rs/zerolog" + "gitlab.33.cn/chat/dtalk/pkg/logger" + "gitlab.33.cn/chat/dtalk/pkg/oss" + "gitlab.33.cn/chat/dtalk/pkg/oss/aliyun" + "gitlab.33.cn/chat/dtalk/pkg/oss/huaweiyun" + "gitlab.33.cn/chat/dtalk/pkg/oss/minio" + "gitlab.33.cn/chat/dtalk/service/oss/config" + "gitlab.33.cn/chat/dtalk/service/oss/dao" + "gitlab.33.cn/chat/dtalk/service/oss/model" +) + +type Service struct { + log zerolog.Logger + cfg *config.Config + dao *dao.Dao + ossEngine map[string]*model.App +} + +func New(cfg *config.Config) *Service { + s := &Service{ + log: logger.New(cfg.Env, "oss"), + cfg: cfg, + dao: dao.New(cfg), + ossEngine: make(map[string]*model.App), + } + s.engineInit() + return s +} + +func (s *Service) Ping() error { + return nil +} + +func (s Service) Config() *config.Config { + return s.cfg +} + +func (s *Service) engineInit() { + for _, cfg := range s.cfg.Oss { + var invoker oss.Oss + ossCfg := convert2Oss(cfg) + switch cfg.OssType { + case model.Oss_Aliyun: + invoker = aliyun.New(ossCfg) + case model.Oss_Huaweiuyn: + invoker = huaweiyun.New(ossCfg) + case model.Oss_Minio: + invoker = minio.New(ossCfg) + default: + s.log.Error().Str("cfg.OssType", cfg.OssType).Msg("oss plugin not find") + continue + } + + app, ok := s.ossEngine[cfg.AppId] + if !ok { + app = model.NewApp(cfg.AppId) + } + app.DefaultOssType = cfg.OssType + app.Register(cfg.OssType, invoker) + s.ossEngine[cfg.AppId] = app + } +} + +func (s *Service) GetEngine(appId string) *model.App { + return s.ossEngine[appId] +} + +func convert2Oss(ossCfg *config.Oss) *oss.Config { + ossConfigs := &oss.Config{} + ossConfigs.RegionId = ossCfg.RegionId + ossConfigs.AccessKeyId = ossCfg.AccessKeyId + ossConfigs.AccessKeySecret = ossCfg.AccessKeySecret + ossConfigs.Role = ossCfg.Role + ossConfigs.Policy = ossCfg.Policy + ossConfigs.DurationSeconds = ossCfg.DurationSeconds + ossConfigs.Bucket = ossCfg.Bucket + ossConfigs.EndPoint = ossCfg.EndPoint + ossConfigs.PublicUrl = ossCfg.PublicUrl + + return ossConfigs +} diff --git a/service/oss/version.go b/service/oss/version.go new file mode 100644 index 0000000..fadf7bd --- /dev/null +++ b/service/oss/version.go @@ -0,0 +1,16 @@ +package version + +//var ( +// // The full version string +// Version = "1.5.0" +// +// // GitCommit is set with --ldflags "-X main.gitCommit=$(git rev-parse --short=8 HEAD)" +// GitCommit string +//) +// +//func GetVersion() string { +// if GitCommit != "" { +// return Version + "-" + GitCommit +// } +// return Version +//} diff --git a/service/record/answer/CHANGELOG.md b/service/record/answer/CHANGELOG.md new file mode 100644 index 0000000..3dbb925 --- /dev/null +++ b/service/record/answer/CHANGELOG.md @@ -0,0 +1,44 @@ +版本号`major.minor.patch`具体规则如下: +- major:主版本号,如有重大版本重构则该字段递增,通常各主版本间接口不兼容。 +- minor:次版本号,各次版本号间接口保持兼容,如有接口新增或优化则该字段递增。 +- patch:补丁号,如有功能改善或缺陷修复则该字段递增。 + +## version 0.6.4 + +**Feature** +- 修改imparse本地依赖为远程仓库 2021_12_07_17_44_30 + +## version 0.6 + +**配置文件更新** + +所有 `[xxxRPCClient]` 增加 `RegAddrs = "127.0.0.1:2379"` 字段 + +**Feature** +- 重构 answer v0.6.0-pre 2021.10.14 +- 更新 etcdv3.5.0 v0.6.3 + + +## version 0.5.3 @2021.8.9 + +**Feature** +- push http接口支持参数comet.Proto的body @v0.5.3 +- 转账交易消息格式支持 @v0.5.4 2021.8.16 +- 将appId和msg engine name分离 v0.5.5 +- 支持@消息类型 v0.5.6 2021.9.30 + +## version 0.5.2 + +**Feature** +- 新增 3 中通知类型 + + +## example x.x.x @yy.mm.dd + +**Feature** + +**Bug Fixes** + +**Improvement** + +**Breaking Change** diff --git a/service/record/answer/Makefile b/service/record/answer/Makefile new file mode 100644 index 0000000..b005131 --- /dev/null +++ b/service/record/answer/Makefile @@ -0,0 +1,43 @@ +# golang1.9 or latest +# 1. make help +# 2. make dep +# 3. make build +# ... + +VERSION := $(shell echo $(shell cat cmd/main.go | grep "projectVersion =" | cut -d '=' -f2)) +APP_NAME := answer +BUILD_DIR := build +APP := ${BUILD_DIR}/${APP_NAME}_v${VERSION} +PKG_NAME := ${APP_NAME}_v${VERSION} +PKG := ${PKG_NAME}.tar.gz + +main_path = "main" +go_version = $(shell go version | awk '{ print $3 }') +build_time = $(shell date "+%Y-%m-%d %H:%M:%S %Z") +git_commit = $(shell git rev-parse --short=10 HEAD) +flags := -ldflags "-X '${main_path}.goVersion=${go_version}' \ +-X '${main_path}.buildTime=${build_time}' \ +-X '${main_path}.gitCommit=${git_commit}' \ +-X 'google.golang.org/protobuf/reflect/protoregistry.conflictPolicy=warn'" + +.PHONY: clean build pkg + +clean: ## Remove previous build + @rm -rf ${BUILD_DIR} + @go clean + +build: #checkgofmt ## Build the binary file + GOOS=linux GOARCH=amd64 GO111MODULE=on GOPROXY=https://goproxy.cn,direct GOSUMDB="sum.golang.google.cn" go build -v $(flags) -o $(APP) cmd/main.go + +pkg: build ## Package + mkdir -p ${PKG_NAME}/bin + mkdir -p ${PKG_NAME}/etc + cp ${APP} ${PKG_NAME}/bin/ + cp etc/* ${PKG_NAME}/etc/ + tar zvcf ${PKG} ${PKG_NAME} + rm -rf ${PKG_NAME} + +REMOTE_BIN_PATH := /opt/dtalk/srv/app/bin +upload: build + rsync -r $(APP) 107:$(REMOTE_BIN_PATH) + ssh 107 "cd $(REMOTE_BIN_PATH) && chmod 777 $(PKG_NAME) && ln -sf $(PKG_NAME) $(APP_NAME) && supervisorctl restart $(APP_NAME)" \ No newline at end of file diff --git a/service/record/answer/api/answer.pb.go b/service/record/answer/api/answer.pb.go new file mode 100644 index 0000000..43035cc --- /dev/null +++ b/service/record/answer/api/answer.pb.go @@ -0,0 +1,727 @@ +// protoc -I=. -I=$GOPATH/src --go_out=plugins=grpc:. *.proto + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc v3.19.1 +// source: answer.proto + +package answer + +import ( + proto "gitlab.33.cn/chat/imparse/proto" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type PushCommonMsgReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + From string `protobuf:"bytes,2,opt,name=from,proto3" json:"from,omitempty"` + Body []byte `protobuf:"bytes,3,opt,name=body,proto3" json:"body,omitempty"` +} + +func (x *PushCommonMsgReq) Reset() { + *x = PushCommonMsgReq{} + if protoimpl.UnsafeEnabled { + mi := &file_answer_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PushCommonMsgReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PushCommonMsgReq) ProtoMessage() {} + +func (x *PushCommonMsgReq) ProtoReflect() protoreflect.Message { + mi := &file_answer_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PushCommonMsgReq.ProtoReflect.Descriptor instead. +func (*PushCommonMsgReq) Descriptor() ([]byte, []int) { + return file_answer_proto_rawDescGZIP(), []int{0} +} + +func (x *PushCommonMsgReq) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *PushCommonMsgReq) GetFrom() string { + if x != nil { + return x.From + } + return "" +} + +func (x *PushCommonMsgReq) GetBody() []byte { + if x != nil { + return x.Body + } + return nil +} + +type PushCommonMsgReply struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Mid int64 `protobuf:"varint,1,opt,name=mid,proto3" json:"mid,omitempty"` + Time uint64 `protobuf:"varint,2,opt,name=time,proto3" json:"time,omitempty"` +} + +func (x *PushCommonMsgReply) Reset() { + *x = PushCommonMsgReply{} + if protoimpl.UnsafeEnabled { + mi := &file_answer_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PushCommonMsgReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PushCommonMsgReply) ProtoMessage() {} + +func (x *PushCommonMsgReply) ProtoReflect() protoreflect.Message { + mi := &file_answer_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PushCommonMsgReply.ProtoReflect.Descriptor instead. +func (*PushCommonMsgReply) Descriptor() ([]byte, []int) { + return file_answer_proto_rawDescGZIP(), []int{1} +} + +func (x *PushCommonMsgReply) GetMid() int64 { + if x != nil { + return x.Mid + } + return 0 +} + +func (x *PushCommonMsgReply) GetTime() uint64 { + if x != nil { + return x.Time + } + return 0 +} + +type PushNoticeMsgReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Seq string `protobuf:"bytes,1,opt,name=seq,proto3" json:"seq,omitempty"` + ChannelType int32 `protobuf:"varint,2,opt,name=channelType,proto3" json:"channelType,omitempty"` + From string `protobuf:"bytes,3,opt,name=from,proto3" json:"from,omitempty"` + Target string `protobuf:"bytes,4,opt,name=target,proto3" json:"target,omitempty"` + Data []byte `protobuf:"bytes,5,opt,name=data,proto3" json:"data,omitempty"` +} + +func (x *PushNoticeMsgReq) Reset() { + *x = PushNoticeMsgReq{} + if protoimpl.UnsafeEnabled { + mi := &file_answer_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PushNoticeMsgReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PushNoticeMsgReq) ProtoMessage() {} + +func (x *PushNoticeMsgReq) ProtoReflect() protoreflect.Message { + mi := &file_answer_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PushNoticeMsgReq.ProtoReflect.Descriptor instead. +func (*PushNoticeMsgReq) Descriptor() ([]byte, []int) { + return file_answer_proto_rawDescGZIP(), []int{2} +} + +func (x *PushNoticeMsgReq) GetSeq() string { + if x != nil { + return x.Seq + } + return "" +} + +func (x *PushNoticeMsgReq) GetChannelType() int32 { + if x != nil { + return x.ChannelType + } + return 0 +} + +func (x *PushNoticeMsgReq) GetFrom() string { + if x != nil { + return x.From + } + return "" +} + +func (x *PushNoticeMsgReq) GetTarget() string { + if x != nil { + return x.Target + } + return "" +} + +func (x *PushNoticeMsgReq) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +type PushNoticeMsgReply struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Mid int64 `protobuf:"varint,1,opt,name=mid,proto3" json:"mid,omitempty"` +} + +func (x *PushNoticeMsgReply) Reset() { + *x = PushNoticeMsgReply{} + if protoimpl.UnsafeEnabled { + mi := &file_answer_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PushNoticeMsgReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PushNoticeMsgReply) ProtoMessage() {} + +func (x *PushNoticeMsgReply) ProtoReflect() protoreflect.Message { + mi := &file_answer_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PushNoticeMsgReply.ProtoReflect.Descriptor instead. +func (*PushNoticeMsgReply) Descriptor() ([]byte, []int) { + return file_answer_proto_rawDescGZIP(), []int{3} +} + +func (x *PushNoticeMsgReply) GetMid() int64 { + if x != nil { + return x.Mid + } + return 0 +} + +type UniCastSignalReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Type proto.SignalType `protobuf:"varint,1,opt,name=type,proto3,enum=imparse.signal.SignalType" json:"type,omitempty"` + Target string `protobuf:"bytes,2,opt,name=target,proto3" json:"target,omitempty"` + Body []byte `protobuf:"bytes,3,opt,name=body,proto3" json:"body,omitempty"` +} + +func (x *UniCastSignalReq) Reset() { + *x = UniCastSignalReq{} + if protoimpl.UnsafeEnabled { + mi := &file_answer_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UniCastSignalReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UniCastSignalReq) ProtoMessage() {} + +func (x *UniCastSignalReq) ProtoReflect() protoreflect.Message { + mi := &file_answer_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UniCastSignalReq.ProtoReflect.Descriptor instead. +func (*UniCastSignalReq) Descriptor() ([]byte, []int) { + return file_answer_proto_rawDescGZIP(), []int{4} +} + +func (x *UniCastSignalReq) GetType() proto.SignalType { + if x != nil { + return x.Type + } + return proto.SignalType(0) +} + +func (x *UniCastSignalReq) GetTarget() string { + if x != nil { + return x.Target + } + return "" +} + +func (x *UniCastSignalReq) GetBody() []byte { + if x != nil { + return x.Body + } + return nil +} + +type UniCastSignalReply struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Mid int64 `protobuf:"varint,1,opt,name=mid,proto3" json:"mid,omitempty"` +} + +func (x *UniCastSignalReply) Reset() { + *x = UniCastSignalReply{} + if protoimpl.UnsafeEnabled { + mi := &file_answer_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UniCastSignalReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UniCastSignalReply) ProtoMessage() {} + +func (x *UniCastSignalReply) ProtoReflect() protoreflect.Message { + mi := &file_answer_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UniCastSignalReply.ProtoReflect.Descriptor instead. +func (*UniCastSignalReply) Descriptor() ([]byte, []int) { + return file_answer_proto_rawDescGZIP(), []int{5} +} + +func (x *UniCastSignalReply) GetMid() int64 { + if x != nil { + return x.Mid + } + return 0 +} + +type GroupCastSignalReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Type proto.SignalType `protobuf:"varint,1,opt,name=type,proto3,enum=imparse.signal.SignalType" json:"type,omitempty"` + Target string `protobuf:"bytes,2,opt,name=target,proto3" json:"target,omitempty"` + Body []byte `protobuf:"bytes,3,opt,name=body,proto3" json:"body,omitempty"` +} + +func (x *GroupCastSignalReq) Reset() { + *x = GroupCastSignalReq{} + if protoimpl.UnsafeEnabled { + mi := &file_answer_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GroupCastSignalReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GroupCastSignalReq) ProtoMessage() {} + +func (x *GroupCastSignalReq) ProtoReflect() protoreflect.Message { + mi := &file_answer_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GroupCastSignalReq.ProtoReflect.Descriptor instead. +func (*GroupCastSignalReq) Descriptor() ([]byte, []int) { + return file_answer_proto_rawDescGZIP(), []int{6} +} + +func (x *GroupCastSignalReq) GetType() proto.SignalType { + if x != nil { + return x.Type + } + return proto.SignalType(0) +} + +func (x *GroupCastSignalReq) GetTarget() string { + if x != nil { + return x.Target + } + return "" +} + +func (x *GroupCastSignalReq) GetBody() []byte { + if x != nil { + return x.Body + } + return nil +} + +type GroupCastSignalReply struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Mid int64 `protobuf:"varint,1,opt,name=mid,proto3" json:"mid,omitempty"` +} + +func (x *GroupCastSignalReply) Reset() { + *x = GroupCastSignalReply{} + if protoimpl.UnsafeEnabled { + mi := &file_answer_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GroupCastSignalReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GroupCastSignalReply) ProtoMessage() {} + +func (x *GroupCastSignalReply) ProtoReflect() protoreflect.Message { + mi := &file_answer_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GroupCastSignalReply.ProtoReflect.Descriptor instead. +func (*GroupCastSignalReply) Descriptor() ([]byte, []int) { + return file_answer_proto_rawDescGZIP(), []int{7} +} + +func (x *GroupCastSignalReply) GetMid() int64 { + if x != nil { + return x.Mid + } + return 0 +} + +var File_answer_proto protoreflect.FileDescriptor + +var file_answer_proto_rawDesc = []byte{ + 0x0a, 0x0c, 0x61, 0x6e, 0x73, 0x77, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0c, + 0x64, 0x74, 0x61, 0x6c, 0x6b, 0x2e, 0x61, 0x6e, 0x73, 0x77, 0x65, 0x72, 0x1a, 0x2c, 0x67, 0x69, + 0x74, 0x6c, 0x61, 0x62, 0x2e, 0x33, 0x33, 0x2e, 0x63, 0x6e, 0x2f, 0x63, 0x68, 0x61, 0x74, 0x2f, + 0x69, 0x6d, 0x70, 0x61, 0x72, 0x73, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x73, 0x69, + 0x67, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x4c, 0x0a, 0x10, 0x50, 0x75, + 0x73, 0x68, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x4d, 0x73, 0x67, 0x52, 0x65, 0x71, 0x12, 0x10, + 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, + 0x12, 0x12, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x22, 0x3a, 0x0a, 0x12, 0x50, 0x75, 0x73, 0x68, + 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x4d, 0x73, 0x67, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x10, + 0x0a, 0x03, 0x6d, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x6d, 0x69, 0x64, + 0x12, 0x12, 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, + 0x74, 0x69, 0x6d, 0x65, 0x22, 0x86, 0x01, 0x0a, 0x10, 0x50, 0x75, 0x73, 0x68, 0x4e, 0x6f, 0x74, + 0x69, 0x63, 0x65, 0x4d, 0x73, 0x67, 0x52, 0x65, 0x71, 0x12, 0x10, 0x0a, 0x03, 0x73, 0x65, 0x71, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x73, 0x65, 0x71, 0x12, 0x20, 0x0a, 0x0b, 0x63, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, + 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x72, 0x6f, + 0x6d, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, + 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x26, 0x0a, + 0x12, 0x50, 0x75, 0x73, 0x68, 0x4e, 0x6f, 0x74, 0x69, 0x63, 0x65, 0x4d, 0x73, 0x67, 0x52, 0x65, + 0x70, 0x6c, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x03, 0x6d, 0x69, 0x64, 0x22, 0x6e, 0x0a, 0x10, 0x55, 0x6e, 0x69, 0x43, 0x61, 0x73, 0x74, + 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x12, 0x2e, 0x0a, 0x04, 0x74, 0x79, 0x70, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x69, 0x6d, 0x70, 0x61, 0x72, 0x73, + 0x65, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x54, + 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x04, 0x62, 0x6f, 0x64, 0x79, 0x22, 0x26, 0x0a, 0x12, 0x55, 0x6e, 0x69, 0x43, 0x61, 0x73, 0x74, + 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6d, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x6d, 0x69, 0x64, 0x22, 0x70, 0x0a, + 0x12, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x43, 0x61, 0x73, 0x74, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, + 0x52, 0x65, 0x71, 0x12, 0x2e, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0e, 0x32, 0x1a, 0x2e, 0x69, 0x6d, 0x70, 0x61, 0x72, 0x73, 0x65, 0x2e, 0x73, 0x69, 0x67, 0x6e, + 0x61, 0x6c, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, + 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x62, + 0x6f, 0x64, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x22, + 0x28, 0x0a, 0x14, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x43, 0x61, 0x73, 0x74, 0x53, 0x69, 0x67, 0x6e, + 0x61, 0x6c, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x6d, 0x69, 0x64, 0x32, 0xda, 0x02, 0x0a, 0x06, 0x41, 0x6e, + 0x73, 0x77, 0x65, 0x72, 0x12, 0x51, 0x0a, 0x0d, 0x50, 0x75, 0x73, 0x68, 0x43, 0x6f, 0x6d, 0x6d, + 0x6f, 0x6e, 0x4d, 0x73, 0x67, 0x12, 0x1e, 0x2e, 0x64, 0x74, 0x61, 0x6c, 0x6b, 0x2e, 0x61, 0x6e, + 0x73, 0x77, 0x65, 0x72, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x4d, + 0x73, 0x67, 0x52, 0x65, 0x71, 0x1a, 0x20, 0x2e, 0x64, 0x74, 0x61, 0x6c, 0x6b, 0x2e, 0x61, 0x6e, + 0x73, 0x77, 0x65, 0x72, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x4d, + 0x73, 0x67, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x51, 0x0a, 0x0d, 0x50, 0x75, 0x73, 0x68, 0x4e, + 0x6f, 0x74, 0x69, 0x63, 0x65, 0x4d, 0x73, 0x67, 0x12, 0x1e, 0x2e, 0x64, 0x74, 0x61, 0x6c, 0x6b, + 0x2e, 0x61, 0x6e, 0x73, 0x77, 0x65, 0x72, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x4e, 0x6f, 0x74, 0x69, + 0x63, 0x65, 0x4d, 0x73, 0x67, 0x52, 0x65, 0x71, 0x1a, 0x20, 0x2e, 0x64, 0x74, 0x61, 0x6c, 0x6b, + 0x2e, 0x61, 0x6e, 0x73, 0x77, 0x65, 0x72, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x4e, 0x6f, 0x74, 0x69, + 0x63, 0x65, 0x4d, 0x73, 0x67, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x51, 0x0a, 0x0d, 0x55, 0x6e, + 0x69, 0x43, 0x61, 0x73, 0x74, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x12, 0x1e, 0x2e, 0x64, 0x74, + 0x61, 0x6c, 0x6b, 0x2e, 0x61, 0x6e, 0x73, 0x77, 0x65, 0x72, 0x2e, 0x55, 0x6e, 0x69, 0x43, 0x61, + 0x73, 0x74, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x1a, 0x20, 0x2e, 0x64, 0x74, + 0x61, 0x6c, 0x6b, 0x2e, 0x61, 0x6e, 0x73, 0x77, 0x65, 0x72, 0x2e, 0x55, 0x6e, 0x69, 0x43, 0x61, + 0x73, 0x74, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x57, 0x0a, + 0x0f, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x43, 0x61, 0x73, 0x74, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, + 0x12, 0x20, 0x2e, 0x64, 0x74, 0x61, 0x6c, 0x6b, 0x2e, 0x61, 0x6e, 0x73, 0x77, 0x65, 0x72, 0x2e, + 0x47, 0x72, 0x6f, 0x75, 0x70, 0x43, 0x61, 0x73, 0x74, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x52, + 0x65, 0x71, 0x1a, 0x22, 0x2e, 0x64, 0x74, 0x61, 0x6c, 0x6b, 0x2e, 0x61, 0x6e, 0x73, 0x77, 0x65, + 0x72, 0x2e, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x43, 0x61, 0x73, 0x74, 0x53, 0x69, 0x67, 0x6e, 0x61, + 0x6c, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x42, 0x2f, 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x6c, 0x61, 0x62, + 0x2e, 0x33, 0x33, 0x2e, 0x63, 0x6e, 0x2f, 0x63, 0x68, 0x61, 0x74, 0x2f, 0x64, 0x74, 0x61, 0x6c, + 0x6b, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, + 0x2f, 0x61, 0x6e, 0x73, 0x77, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_answer_proto_rawDescOnce sync.Once + file_answer_proto_rawDescData = file_answer_proto_rawDesc +) + +func file_answer_proto_rawDescGZIP() []byte { + file_answer_proto_rawDescOnce.Do(func() { + file_answer_proto_rawDescData = protoimpl.X.CompressGZIP(file_answer_proto_rawDescData) + }) + return file_answer_proto_rawDescData +} + +var file_answer_proto_msgTypes = make([]protoimpl.MessageInfo, 8) +var file_answer_proto_goTypes = []interface{}{ + (*PushCommonMsgReq)(nil), // 0: dtalk.answer.PushCommonMsgReq + (*PushCommonMsgReply)(nil), // 1: dtalk.answer.PushCommonMsgReply + (*PushNoticeMsgReq)(nil), // 2: dtalk.answer.PushNoticeMsgReq + (*PushNoticeMsgReply)(nil), // 3: dtalk.answer.PushNoticeMsgReply + (*UniCastSignalReq)(nil), // 4: dtalk.answer.UniCastSignalReq + (*UniCastSignalReply)(nil), // 5: dtalk.answer.UniCastSignalReply + (*GroupCastSignalReq)(nil), // 6: dtalk.answer.GroupCastSignalReq + (*GroupCastSignalReply)(nil), // 7: dtalk.answer.GroupCastSignalReply + (proto.SignalType)(0), // 8: imparse.signal.SignalType +} +var file_answer_proto_depIdxs = []int32{ + 8, // 0: dtalk.answer.UniCastSignalReq.type:type_name -> imparse.signal.SignalType + 8, // 1: dtalk.answer.GroupCastSignalReq.type:type_name -> imparse.signal.SignalType + 0, // 2: dtalk.answer.Answer.PushCommonMsg:input_type -> dtalk.answer.PushCommonMsgReq + 2, // 3: dtalk.answer.Answer.PushNoticeMsg:input_type -> dtalk.answer.PushNoticeMsgReq + 4, // 4: dtalk.answer.Answer.UniCastSignal:input_type -> dtalk.answer.UniCastSignalReq + 6, // 5: dtalk.answer.Answer.GroupCastSignal:input_type -> dtalk.answer.GroupCastSignalReq + 1, // 6: dtalk.answer.Answer.PushCommonMsg:output_type -> dtalk.answer.PushCommonMsgReply + 3, // 7: dtalk.answer.Answer.PushNoticeMsg:output_type -> dtalk.answer.PushNoticeMsgReply + 5, // 8: dtalk.answer.Answer.UniCastSignal:output_type -> dtalk.answer.UniCastSignalReply + 7, // 9: dtalk.answer.Answer.GroupCastSignal:output_type -> dtalk.answer.GroupCastSignalReply + 6, // [6:10] is the sub-list for method output_type + 2, // [2:6] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_answer_proto_init() } +func file_answer_proto_init() { + if File_answer_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_answer_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PushCommonMsgReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_answer_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PushCommonMsgReply); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_answer_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PushNoticeMsgReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_answer_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PushNoticeMsgReply); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_answer_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UniCastSignalReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_answer_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UniCastSignalReply); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_answer_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GroupCastSignalReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_answer_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GroupCastSignalReply); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_answer_proto_rawDesc, + NumEnums: 0, + NumMessages: 8, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_answer_proto_goTypes, + DependencyIndexes: file_answer_proto_depIdxs, + MessageInfos: file_answer_proto_msgTypes, + }.Build() + File_answer_proto = out.File + file_answer_proto_rawDesc = nil + file_answer_proto_goTypes = nil + file_answer_proto_depIdxs = nil +} diff --git a/service/record/answer/api/answer.proto b/service/record/answer/api/answer.proto new file mode 100644 index 0000000..4a8dd32 --- /dev/null +++ b/service/record/answer/api/answer.proto @@ -0,0 +1,57 @@ +// protoc -I=. -I=$GOPATH/src --go_out=plugins=grpc:. *.proto +syntax = "proto3"; + +import "gitlab.33.cn/chat/imparse/proto/signal.proto"; + +package dtalk.answer; +option go_package = "gitlab.33.cn/chat/dtalk/service/record/answer"; + +message PushCommonMsgReq { + string key = 1; + string from = 2; + bytes body = 3; +} + +message PushCommonMsgReply { + int64 mid = 1; + uint64 time = 2; +} + +message PushNoticeMsgReq { + string seq = 1; + int32 channelType = 2; + string from = 3; + string target = 4; + bytes data = 5; +} + +message PushNoticeMsgReply { + int64 mid = 1; +} + +message UniCastSignalReq { + imparse.signal.SignalType type = 1; + string target = 2; + bytes body = 3; +} + +message UniCastSignalReply { + int64 mid = 1; +} + +message GroupCastSignalReq { + imparse.signal.SignalType type = 1; + string target = 2; + bytes body = 3; +} + +message GroupCastSignalReply { + int64 mid = 1; +} + +service Answer { + rpc PushCommonMsg(PushCommonMsgReq) returns (PushCommonMsgReply); + rpc PushNoticeMsg(PushNoticeMsgReq) returns (PushNoticeMsgReply); + rpc UniCastSignal(UniCastSignalReq) returns (UniCastSignalReply); + rpc GroupCastSignal(GroupCastSignalReq) returns (GroupCastSignalReply); +} \ No newline at end of file diff --git a/service/record/answer/api/answer_grpc.pb.go b/service/record/answer/api/answer_grpc.pb.go new file mode 100644 index 0000000..f795c32 --- /dev/null +++ b/service/record/answer/api/answer_grpc.pb.go @@ -0,0 +1,209 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. + +package answer + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// AnswerClient is the client API for Answer service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type AnswerClient interface { + PushCommonMsg(ctx context.Context, in *PushCommonMsgReq, opts ...grpc.CallOption) (*PushCommonMsgReply, error) + PushNoticeMsg(ctx context.Context, in *PushNoticeMsgReq, opts ...grpc.CallOption) (*PushNoticeMsgReply, error) + UniCastSignal(ctx context.Context, in *UniCastSignalReq, opts ...grpc.CallOption) (*UniCastSignalReply, error) + GroupCastSignal(ctx context.Context, in *GroupCastSignalReq, opts ...grpc.CallOption) (*GroupCastSignalReply, error) +} + +type answerClient struct { + cc grpc.ClientConnInterface +} + +func NewAnswerClient(cc grpc.ClientConnInterface) AnswerClient { + return &answerClient{cc} +} + +func (c *answerClient) PushCommonMsg(ctx context.Context, in *PushCommonMsgReq, opts ...grpc.CallOption) (*PushCommonMsgReply, error) { + out := new(PushCommonMsgReply) + err := c.cc.Invoke(ctx, "/dtalk.answer.Answer/PushCommonMsg", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *answerClient) PushNoticeMsg(ctx context.Context, in *PushNoticeMsgReq, opts ...grpc.CallOption) (*PushNoticeMsgReply, error) { + out := new(PushNoticeMsgReply) + err := c.cc.Invoke(ctx, "/dtalk.answer.Answer/PushNoticeMsg", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *answerClient) UniCastSignal(ctx context.Context, in *UniCastSignalReq, opts ...grpc.CallOption) (*UniCastSignalReply, error) { + out := new(UniCastSignalReply) + err := c.cc.Invoke(ctx, "/dtalk.answer.Answer/UniCastSignal", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *answerClient) GroupCastSignal(ctx context.Context, in *GroupCastSignalReq, opts ...grpc.CallOption) (*GroupCastSignalReply, error) { + out := new(GroupCastSignalReply) + err := c.cc.Invoke(ctx, "/dtalk.answer.Answer/GroupCastSignal", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// AnswerServer is the server API for Answer service. +// All implementations must embed UnimplementedAnswerServer +// for forward compatibility +type AnswerServer interface { + PushCommonMsg(context.Context, *PushCommonMsgReq) (*PushCommonMsgReply, error) + PushNoticeMsg(context.Context, *PushNoticeMsgReq) (*PushNoticeMsgReply, error) + UniCastSignal(context.Context, *UniCastSignalReq) (*UniCastSignalReply, error) + GroupCastSignal(context.Context, *GroupCastSignalReq) (*GroupCastSignalReply, error) + mustEmbedUnimplementedAnswerServer() +} + +// UnimplementedAnswerServer must be embedded to have forward compatible implementations. +type UnimplementedAnswerServer struct { +} + +func (UnimplementedAnswerServer) PushCommonMsg(context.Context, *PushCommonMsgReq) (*PushCommonMsgReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method PushCommonMsg not implemented") +} +func (UnimplementedAnswerServer) PushNoticeMsg(context.Context, *PushNoticeMsgReq) (*PushNoticeMsgReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method PushNoticeMsg not implemented") +} +func (UnimplementedAnswerServer) UniCastSignal(context.Context, *UniCastSignalReq) (*UniCastSignalReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method UniCastSignal not implemented") +} +func (UnimplementedAnswerServer) GroupCastSignal(context.Context, *GroupCastSignalReq) (*GroupCastSignalReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method GroupCastSignal not implemented") +} +func (UnimplementedAnswerServer) mustEmbedUnimplementedAnswerServer() {} + +// UnsafeAnswerServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to AnswerServer will +// result in compilation errors. +type UnsafeAnswerServer interface { + mustEmbedUnimplementedAnswerServer() +} + +func RegisterAnswerServer(s grpc.ServiceRegistrar, srv AnswerServer) { + s.RegisterService(&Answer_ServiceDesc, srv) +} + +func _Answer_PushCommonMsg_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PushCommonMsgReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AnswerServer).PushCommonMsg(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.answer.Answer/PushCommonMsg", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AnswerServer).PushCommonMsg(ctx, req.(*PushCommonMsgReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Answer_PushNoticeMsg_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PushNoticeMsgReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AnswerServer).PushNoticeMsg(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.answer.Answer/PushNoticeMsg", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AnswerServer).PushNoticeMsg(ctx, req.(*PushNoticeMsgReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Answer_UniCastSignal_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UniCastSignalReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AnswerServer).UniCastSignal(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.answer.Answer/UniCastSignal", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AnswerServer).UniCastSignal(ctx, req.(*UniCastSignalReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Answer_GroupCastSignal_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GroupCastSignalReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AnswerServer).GroupCastSignal(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.answer.Answer/GroupCastSignal", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AnswerServer).GroupCastSignal(ctx, req.(*GroupCastSignalReq)) + } + return interceptor(ctx, in, info, handler) +} + +// Answer_ServiceDesc is the grpc.ServiceDesc for Answer service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Answer_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "dtalk.answer.Answer", + HandlerType: (*AnswerServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "PushCommonMsg", + Handler: _Answer_PushCommonMsg_Handler, + }, + { + MethodName: "PushNoticeMsg", + Handler: _Answer_PushNoticeMsg_Handler, + }, + { + MethodName: "UniCastSignal", + Handler: _Answer_UniCastSignal_Handler, + }, + { + MethodName: "GroupCastSignal", + Handler: _Answer_GroupCastSignal_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "answer.proto", +} diff --git a/service/record/answer/api/client.go b/service/record/answer/api/client.go new file mode 100644 index 0000000..4363075 --- /dev/null +++ b/service/record/answer/api/client.go @@ -0,0 +1,82 @@ +package answer + +import ( + "context" + "fmt" + "time" + + "gitlab.33.cn/chat/dtalk/pkg/naming" + xgrpc "gitlab.33.cn/chat/dtalk/pkg/net/grpc" + "gitlab.33.cn/chat/im-pkg/trace" + xproto "gitlab.33.cn/chat/imparse/proto" + "google.golang.org/grpc" + "google.golang.org/grpc/resolver" +) + +type Client struct { + client AnswerClient +} + +func New(etcdAddr, schema, srvName string, dial time.Duration) *Client { + rb := naming.NewResolver(etcdAddr, schema) + resolver.Register(rb) + + addr := fmt.Sprintf("%s:///%s", schema, srvName) // "schema://[authority]/service" + fmt.Println("answer rpc client call addr:", addr) + + conn, err := xgrpc.NewGRPCConnWithOpts(addr, dial, grpc.WithUnaryInterceptor(trace.OpentracingClientInterceptor)) + if err != nil { + panic(err) + } + return &Client{ + client: NewAnswerClient(conn), + } +} + +func (c *Client) PushCommonMsg(ctx context.Context, key, From string, body []byte) (int64, uint64, error) { + in := &PushCommonMsgReq{ + Key: key, + From: From, + Body: body, + } + res, err := c.client.PushCommonMsg(ctx, in) + if err != nil { + return 0, 0, err + } + return res.Mid, res.Time, err +} + +func (c *Client) PushNoticeMsg(ctx context.Context, Seq string, ChannelType int32, From, Target string, Data []byte) (int64, error) { + in := &PushNoticeMsgReq{ + Seq: Seq, + ChannelType: ChannelType, + From: From, + Target: Target, + Data: Data, + } + res, err := c.client.PushNoticeMsg(ctx, in) + if err != nil { + return 0, err + } + return res.Mid, err +} + +func (c *Client) UniCastSignal(ctx context.Context, Action xproto.SignalType, Target string, Body []byte) error { + in := &UniCastSignalReq{ + Type: Action, + Target: Target, + Body: Body, + } + _, err := c.client.UniCastSignal(ctx, in) + return err +} + +func (c *Client) GroupCastSignal(ctx context.Context, Action xproto.SignalType, Target string, Body []byte) error { + in := &GroupCastSignalReq{ + Type: Action, + Target: Target, + Body: Body, + } + _, err := c.client.GroupCastSignal(ctx, in) + return err +} diff --git a/service/record/answer/api/create.sh b/service/record/answer/api/create.sh new file mode 100644 index 0000000..0e5dbf9 --- /dev/null +++ b/service/record/answer/api/create.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +protoc -I. -I$GOPATH/src \ + --go_out=. --go_opt=paths=source_relative \ + --go-grpc_out=. --go-grpc_opt=paths=source_relative \ + *.proto \ No newline at end of file diff --git a/service/record/answer/cmd/main.go b/service/record/answer/cmd/main.go new file mode 100644 index 0000000..bee5cdf --- /dev/null +++ b/service/record/answer/cmd/main.go @@ -0,0 +1,131 @@ +package main + +import ( + "context" + "flag" + "fmt" + "net" + "os" + "os/signal" + "syscall" + "time" + + "github.com/Terry-Mao/goim/pkg/ip" + "github.com/opentracing/opentracing-go" + "github.com/rs/zerolog/log" + xlog "gitlab.33.cn/chat/dtalk/pkg/log" + "gitlab.33.cn/chat/dtalk/pkg/naming" + "gitlab.33.cn/chat/dtalk/service/record/answer/config" + "gitlab.33.cn/chat/dtalk/service/record/answer/server/grpc" + "gitlab.33.cn/chat/dtalk/service/record/answer/service" + "gitlab.33.cn/chat/im-pkg/trace" +) + +const srvName = "answer" + +var ( + // projectVersion 项目版本 + projectVersion = "0.6.6" + // goVersion go版本 + goVersion = "" + // gitCommit git提交commit id + gitCommit = "" + // buildTime 编译时间 + buildTime = "" + + isShowVersion = flag.Bool("v", false, "show project version") +) + +// showVersion 显示项目版本信息 +func showVersion(isShow bool) { + if isShow { + fmt.Printf("Project: %s\n", srvName) + fmt.Printf(" Version: %s\n", projectVersion) + fmt.Printf(" Go Version: %s\n", goVersion) + fmt.Printf(" Git Commit: %s\n", gitCommit) + fmt.Printf(" Build Time: %s\n", buildTime) + os.Exit(0) + } +} + +// @Title 聊天单模块集成测试 +// @Version 0.1 +// @Description +// @SecurityDefinitions.ApiKey ApiKeyAuth +// @In header +// @Name Authorization +// @BasePath / +func main() { + flag.Parse() + showVersion(*isShowVersion) + + if err := config.Init(); err != nil { + panic(err) + } + // log init + logger, err := xlog.Init(config.Conf.Log) + if err != nil { + panic(err) + } + // set global log instance + log.Logger = logger.With().Str("service", srvName).Logger() + log.Info(). + Str("AppId", config.Conf.AppId). + Str("Env", config.Conf.Env). + Interface("Reg", config.Conf.Reg). + Interface("IdGenerator", config.Conf.IdGenRPCClient). + Interface("GRPCServer", config.Conf.GRPCServer). + Interface("LogicAddr", config.Conf.LogicRPCClient). + Interface("Redis", config.Conf.Redis). + Interface("MQSub", config.Conf.MQSub). + Interface("MQPub", config.Conf.MQPub). + Msg("config info") + + // trace init + tracer, tracerCloser := trace.Init(srvName, config.Conf.Trace) + //不然后续不会有Jaeger实例 + opentracing.SetGlobalTracer(tracer) + + // service init + svc := service.New(config.Conf) + rpc := grpc.New(config.Conf.GRPCServer, svc, log.Logger) + svc.ListenMQ() + + // register server + _, port, _ := net.SplitHostPort(config.Conf.GRPCServer.Addr) + addr := fmt.Sprintf("%s:%s", ip.InternalIP(), port) + if err := naming.Register(config.Conf.Reg.RegAddrs, config.Conf.Reg.SrvName, addr, config.Conf.Reg.Schema, 15); err != nil { + panic(err) + } + log.Info().Msg("register ok") + + // init signal + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT) + for { + s := <-c + log.Info().Str("signal", s.String()).Msg("service get a signal") + switch s { + case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT: + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := rpc.Shutdown(ctx); err != nil { + log.Error().Err(err).Msg("rpc.Shutdown") + } + if err := naming.UnRegister(config.Conf.Reg.SrvName, addr, config.Conf.Reg.Schema); err != nil { + log.Error().Err(err).Msg("naming.UnRegister") + } + if err := tracerCloser.Close(); err != nil { + log.Error().Err(err).Msg("tracer close failed") + } + svc.Shutdown(ctx) + time.Sleep(time.Second * 2) + log.Info().Msg("server exit") + return + case syscall.SIGHUP: + // TODO reload + default: + return + } + } +} diff --git a/service/record/answer/config/answer.toml b/service/record/answer/config/answer.toml new file mode 100644 index 0000000..d6b84e4 --- /dev/null +++ b/service/record/answer/config/answer.toml @@ -0,0 +1,75 @@ +AppId = "dtalk" +Engine = "standard" +Env = "debug" + +[log] +Level="debug" +Mode="console" +Path="" +Display="json" + +[Trace] + ServiceName = "answer" +Gen128Bit = true +[Trace.Sampler] + Type="const" + Param=1.0 +[Trace.Reporter] +LogSpans = true +LocalAgentHostPort = "127.0.0.1:6831" + +[GRPCServer] +Network= "tcp" +Addr= ":30004" +Timeout= "1s" +KeepAliveMaxConnectionIdle= "60s" +KeepAliveMaxConnectionAge= "2h" +KeepAliveMaxMaxConnectionAgeGrace= "20s" +KeepAliveTime= "60s" +KeepAliveTimeout= "20s" + +[reg] +schema = "dtalk" +srvName = "answer" +regAddrs = "127.0.0.1:2379" + +[LogicRPCClient] +regAddrs = "127.0.0.1:2379" +Schema = "im" +SrvName = "logic" +Dial = "1s" +Timeout = "1s" + +[idGenRPCClient] +regAddrs = "127.0.0.1:2379" +schema = "dtalk" +srvName = "generator" +dial = "1s" +timeout = "1s" + +[GroupRPCClient] +regAddrs = "127.0.0.1:2379" +schema = "dtalk" +srvName = "group" +dial = "1s" +timeout = "1s" + +[Redis] +network = "tcp" +addr = "127.0.0.1:6379" +auth = "" +active = 60000 +idle = 1024 +dialTimeout = "200ms" +readTimeout = "500ms" +writeTimeout = "500ms" +idleTimeout = "120s" +expire = "30m" + +[MQSub] +Brokers = ["127.0.0.1:9092"] +Number = 16 +MaxWorker = 1024 + +[MQPub] +Brokers = ["127.0.0.1:9092"] \ No newline at end of file diff --git a/service/record/answer/config/config.go b/service/record/answer/config/config.go new file mode 100644 index 0000000..008fa36 --- /dev/null +++ b/service/record/answer/config/config.go @@ -0,0 +1,180 @@ +package config + +import ( + "flag" + "github.com/uber/jaeger-client-go" + traceConfig "github.com/uber/jaeger-client-go/config" + xlog "gitlab.33.cn/chat/dtalk/pkg/log" + "os" + "time" + + "github.com/BurntSushi/toml" + "gitlab.33.cn/chat/dtalk/pkg/net/grpc" + xgrpc "gitlab.33.cn/chat/dtalk/pkg/net/grpc" + xtime "gitlab.33.cn/chat/dtalk/pkg/time" +) + +var ( + confPath string + regAddrs string + env string + + Conf *Config +) + +func init() { + var ( + defAddrs = os.Getenv("REGADDRS") + defEnv = os.Getenv("DTALKENV") + ) + flag.StringVar(&confPath, "conf", "answer.toml", "default config path.") + flag.StringVar(®Addrs, "reg", defAddrs, "etcd register addrs. eg:127.0.0.1:2379") + flag.StringVar(&env, "env", defEnv, "service runtime environment") +} + +// Init init config. +func Init() (err error) { + Conf = Default() + _, err = toml.DecodeFile(confPath, &Conf) + return +} + +func Default() *Config { + return &Config{ + AppId: "dtalk", + Engine: "standard", + Env: env, + Log: xlog.Config{ + Level: xlog.DebugLevel, + Mode: xlog.ConsoleMode, + Path: "", + Display: xlog.JsonDisplay, + }, + Trace: traceConfig.Configuration{ + ServiceName: "answer", + Gen128Bit: true, + Sampler: &traceConfig.SamplerConfig{ + Type: jaeger.SamplerTypeConst, + Param: 1, + }, + Reporter: &traceConfig.ReporterConfig{ + LogSpans: true, + LocalAgentHostPort: "127.0.0.1:6831", + }, + }, + GRPCServer: &xgrpc.ServerConfig{ + Network: "tcp", + Addr: ":30002", + Timeout: xtime.Duration(time.Second), + KeepAliveMaxConnectionIdle: xtime.Duration(time.Second * 60), + KeepAliveMaxConnectionAge: xtime.Duration(time.Hour * 2), + KeepAliveMaxMaxConnectionAgeGrace: xtime.Duration(time.Second * 20), + KeepAliveTime: xtime.Duration(time.Second * 60), + KeepAliveTimeout: xtime.Duration(time.Second * 20), + }, + Reg: &Reg{ + Schema: "dtalk", + SrvName: "answer", + RegAddrs: regAddrs, + }, + Redis: &Redis{ + Network: "tcp", + Addr: "127.0.0.1:6379", + Auth: "", + Active: 60000, + Idle: 1024, + DialTimeout: xtime.Duration(200 * time.Millisecond), + ReadTimeout: xtime.Duration(500 * time.Millisecond), + WriteTimeout: xtime.Duration(500 * time.Millisecond), + IdleTimeout: xtime.Duration(120 * time.Second), + Expire: xtime.Duration(30 * time.Minute), + }, + IdGenRPCClient: &RPCClient{ + RegAddrs: "127.0.0.1:2379", + Schema: "dtalk", + SrvName: "generator", + Dial: xtime.Duration(time.Second), + Timeout: xtime.Duration(time.Second), + }, + LogicRPCClient: &RPCClient{ + RegAddrs: "127.0.0.1:2379", + Schema: "im", + SrvName: "logic", + Dial: xtime.Duration(time.Second), + Timeout: xtime.Duration(time.Second), + }, + GroupRPCClient: &RPCClient{ + RegAddrs: "127.0.0.1:2379", + Schema: "dtalk", + SrvName: "group", + Dial: xtime.Duration(time.Second), + Timeout: xtime.Duration(time.Second), + }, + MQSub: &MQSubClient{ + Brokers: []string{"127.0.0.1:9092"}, + Number: 16, + MaxWorker: 1024, + }, + MQPub: &MQPubServer{ + Brokers: []string{"127.0.0.1:9092"}, + }, + } +} + +type Config struct { + AppId string + Engine string + Env string + Log xlog.Config + Trace traceConfig.Configuration + //gRPC server + GRPCServer *grpc.ServerConfig + Reg *Reg + Redis *Redis + IdGenRPCClient *RPCClient + LogicRPCClient *RPCClient + GroupRPCClient *RPCClient + MQSub *MQSubClient + MQPub *MQPubServer +} + +// Redis . +type Redis struct { + Network string + Addr string + Auth string + Active int + Idle int + DialTimeout xtime.Duration + ReadTimeout xtime.Duration + WriteTimeout xtime.Duration + IdleTimeout xtime.Duration + Expire xtime.Duration +} + +// Reg is service register/discovery config +type Reg struct { + Schema string + SrvName string // call + RegAddrs string // etcd addrs, seperate by ',' +} + +// RPCClient is RPC client config. +type RPCClient struct { + RegAddrs string // etcd addrs, seperate by ',' + Schema string + SrvName string // call + Dial xtime.Duration + Timeout xtime.Duration +} + +type MQSubClient struct { + Brokers []string + Number uint32 + MaxWorker int +} + +// Kafka . +type MQPubServer struct { + Brokers []string +} diff --git a/service/record/answer/dao/dao.go b/service/record/answer/dao/dao.go new file mode 100644 index 0000000..5920c74 --- /dev/null +++ b/service/record/answer/dao/dao.go @@ -0,0 +1,85 @@ +package dao + +import ( + "fmt" + "time" + + "github.com/gomodule/redigo/redis" + "gitlab.33.cn/chat/dtalk/pkg/naming" + "gitlab.33.cn/chat/dtalk/pkg/net/grpc" + idgen "gitlab.33.cn/chat/dtalk/service/generator/api" + groupApi "gitlab.33.cn/chat/dtalk/service/group/api" + "gitlab.33.cn/chat/dtalk/service/record/answer/config" + "gitlab.33.cn/chat/dtalk/service/record/kafka/publisher" + "google.golang.org/grpc/resolver" + kafka "gopkg.in/Shopify/sarama.v1" +) + +type Dao struct { + appId string + //log zerolog.Logger + redis *redis.Pool + mqPub kafka.SyncProducer + idGenRPCClient idgen.GeneratorClient + groupRPCClient groupApi.GroupClient +} + +func New(c *config.Config) *Dao { + d := &Dao{ + appId: c.AppId, + //log: zlog.Logger, + redis: newRedis(c.Redis), + mqPub: publisher.NewKafkaPub(c.MQPub.Brokers), + idGenRPCClient: newIdGenClient(c), + groupRPCClient: newGroupClient(c), + } + return d +} + +func newRedis(c *config.Redis) *redis.Pool { + return &redis.Pool{ + MaxIdle: c.Idle, + MaxActive: c.Active, + IdleTimeout: time.Duration(c.IdleTimeout), + Dial: func() (redis.Conn, error) { + conn, err := redis.Dial(c.Network, c.Addr, + redis.DialConnectTimeout(time.Duration(c.DialTimeout)), + redis.DialReadTimeout(time.Duration(c.ReadTimeout)), + redis.DialWriteTimeout(time.Duration(c.WriteTimeout)), + redis.DialPassword(c.Auth), + ) + if err != nil { + return nil, err + } + return conn, nil + }, + } +} + +func newIdGenClient(cfg *config.Config) idgen.GeneratorClient { + rb := naming.NewResolver(cfg.IdGenRPCClient.RegAddrs, cfg.IdGenRPCClient.Schema) + resolver.Register(rb) + + addr := fmt.Sprintf("%s:///%s", cfg.IdGenRPCClient.Schema, cfg.IdGenRPCClient.SrvName) // "schema://[authority]/service" + fmt.Println("rpc client call addr:", addr) + + conn, err := grpc.NewGRPCConn(addr, time.Duration(cfg.IdGenRPCClient.Dial)) + if err != nil { + panic(err) + } + return idgen.NewGeneratorClient(conn) +} + +func newGroupClient(cfg *config.Config) groupApi.GroupClient { + rb := naming.NewResolver(cfg.GroupRPCClient.RegAddrs, cfg.GroupRPCClient.Schema) + resolver.Register(rb) + + addr := fmt.Sprintf("%s:///%s", cfg.GroupRPCClient.Schema, cfg.GroupRPCClient.SrvName) // "schema://[authority]/service" + fmt.Println("rpc client call addr:", addr) + + conn, err := grpc.NewGRPCConn(addr, time.Duration(cfg.GroupRPCClient.Dial)) + if err != nil { + panic(err) + } + return groupApi.NewGroupClient(conn) +} diff --git a/service/record/answer/dao/dao_test.go b/service/record/answer/dao/dao_test.go new file mode 100644 index 0000000..bf80ce4 --- /dev/null +++ b/service/record/answer/dao/dao_test.go @@ -0,0 +1,35 @@ +package dao + +import ( + "os" + "testing" + "time" + + "github.com/gomodule/redigo/redis" + "github.com/rs/zerolog" + zlog "github.com/rs/zerolog/log" + xtime "gitlab.33.cn/chat/dtalk/pkg/time" + "gitlab.33.cn/chat/dtalk/service/record/answer/config" +) + +var ( + testLog zerolog.Logger + testRedis *redis.Pool +) + +func TestMain(m *testing.M) { + testLog = zlog.Logger + testRedis = newRedis(&config.Redis{ + Network: "tcp", + Addr: "127.0.0.1:6379", + Auth: "", + Active: 60000, + Idle: 1024, + DialTimeout: xtime.Duration(200 * time.Millisecond), + ReadTimeout: xtime.Duration(500 * time.Millisecond), + WriteTimeout: xtime.Duration(500 * time.Millisecond), + IdleTimeout: xtime.Duration(120 * time.Second), + Expire: xtime.Duration(30 * time.Minute), + }) + os.Exit(m.Run()) +} diff --git a/service/record/answer/dao/group.go b/service/record/answer/dao/group.go new file mode 100644 index 0000000..7eaa4ea --- /dev/null +++ b/service/record/answer/dao/group.go @@ -0,0 +1,24 @@ +package dao + +import ( + "context" + groupApi "gitlab.33.cn/chat/dtalk/service/group/api" + "time" +) + +func (d *Dao) CheckInGroup(ctx context.Context, uid string, gid int64) (isOk bool, err error) { + var ( + req groupApi.CheckInGroupRequest + reply *groupApi.CheckInGroupReply + ) + req.MemberId = uid + req.GroupId = gid + ctx, cancel := context.WithDeadline(ctx, time.Now().Add(time.Second*3)) + defer cancel() + reply, err = d.groupRPCClient.CheckInGroup(ctx, &req) + if err != nil { + return + } + + return reply.IsOk, nil +} diff --git a/service/record/answer/dao/idg.go b/service/record/answer/dao/idg.go new file mode 100644 index 0000000..97b46c9 --- /dev/null +++ b/service/record/answer/dao/idg.go @@ -0,0 +1,23 @@ +package dao + +import ( + "context" + idgen "gitlab.33.cn/chat/dtalk/service/generator/api" + "time" +) + +// Receive receive a message. +func (d *Dao) GetMid(ctx context.Context) (id int64, err error) { + var ( + req idgen.Empty + reply *idgen.GetIDReply + ) + ctx, cancel := context.WithDeadline(ctx, time.Now().Add(time.Second*3)) + defer cancel() + reply, err = d.idGenRPCClient.GetID(ctx, &req) + if err != nil { + return + } + + return reply.Id, nil +} diff --git a/service/record/answer/dao/kafka.go b/service/record/answer/dao/kafka.go new file mode 100644 index 0000000..52475d3 --- /dev/null +++ b/service/record/answer/dao/kafka.go @@ -0,0 +1,21 @@ +package dao + +import ( + "context" + "fmt" + "gopkg.in/Shopify/sarama.v1" +) + +// PushMsg push a message to databus. +func (d *Dao) PublishToSend(ctx context.Context, fromId string, data []byte) (err error) { + appTopic := fmt.Sprintf("received-%s-topic", d.appId) + m := &sarama.ProducerMessage{ + Key: sarama.StringEncoder(fromId), + Topic: appTopic, + Value: sarama.ByteEncoder(data), + } + if _, _, err = d.mqPub.SendMessage(m); err != nil { + return + } + return +} diff --git a/service/record/answer/dao/redis.go b/service/record/answer/dao/redis.go new file mode 100644 index 0000000..c1ed9ba --- /dev/null +++ b/service/record/answer/dao/redis.go @@ -0,0 +1,56 @@ +package dao + +import ( + "encoding/json" + "fmt" + + "github.com/gomodule/redigo/redis" + "gitlab.33.cn/chat/dtalk/service/record/answer/model" +) + +const ( + _prefixRecordSeq = "record-seq:%v" +) + +func keyUserRecordSeq(uid string) string { + return fmt.Sprintf(_prefixRecordSeq, uid) +} + +func (d *Dao) AddRecordSeqIndex(uid string, m *model.MsgIndex) error { + val, err := json.Marshal(m) + if err != nil { + return err + } + key := keyUserRecordSeq(uid) + conn := d.redis.Get() + defer conn.Close() + if err := conn.Send("HSET", key, m.Seq, val); err != nil { + return err + } + if err := conn.Flush(); err != nil { + return err + } + if _, err := conn.Receive(); err != nil { + return err + } + return nil +} + +func (d *Dao) GetRecordSeqIndex(uid, seq string) (*model.MsgIndex, error) { + key := keyUserRecordSeq(uid) + conn := d.redis.Get() + defer conn.Close() + val, err := redis.String(conn.Do("HGET", key, seq)) + if err != nil { + if err == redis.ErrNil { + return nil, nil + } + return nil, err + } + item := model.MsgIndex{} + err = json.Unmarshal([]byte(val), &item) + if err != nil { + return nil, err + } + return &item, nil +} diff --git a/service/record/answer/dao/redis_test.go b/service/record/answer/dao/redis_test.go new file mode 100644 index 0000000..32f3f9c --- /dev/null +++ b/service/record/answer/dao/redis_test.go @@ -0,0 +1,120 @@ +package dao + +import ( + "testing" + + "github.com/gomodule/redigo/redis" + "github.com/rs/zerolog" + "gitlab.33.cn/chat/dtalk/service/record/answer/model" +) + +func TestDao_AddRecordSeqIndex(t *testing.T) { + type fields struct { + appId string + log zerolog.Logger + redis *redis.Pool + } + type args struct { + uid string + m *model.MsgIndex + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "", + fields: fields{ + appId: "dtalk", + log: testLog, + redis: testRedis, + }, + args: args{ + uid: "2", + m: &model.MsgIndex{ + Mid: "1", + Seq: "1", + SenderId: "2", + CreateTime: 0, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + d := &Dao{ + appId: tt.fields.appId, + log: tt.fields.log, + redis: tt.fields.redis, + } + if err := d.AddRecordSeqIndex(tt.args.uid, tt.args.m); (err != nil) != tt.wantErr { + t.Errorf("AddRecordSeqIndex() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestDao_GetRecordSeqIndex(t *testing.T) { + type fields struct { + appId string + log zerolog.Logger + redis *redis.Pool + } + type args struct { + uid string + seq string + } + tests := []struct { + name string + fields fields + args args + want *model.MsgIndex + wantErr bool + }{ + { + name: "", + fields: fields{ + appId: "dtalk", + log: testLog, + redis: testRedis, + }, + args: args{ + uid: "2", + seq: "1", + }, + want: nil, + wantErr: false, + }, { + name: "", + fields: fields{ + appId: "dtalk", + log: testLog, + redis: testRedis, + }, + args: args{ + uid: "2", + seq: "2", + }, + want: nil, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + d := &Dao{ + appId: tt.fields.appId, + log: tt.fields.log, + redis: tt.fields.redis, + } + got, err := d.GetRecordSeqIndex(tt.args.uid, tt.args.seq) + if (err != nil) != tt.wantErr { + t.Errorf("GetRecordSeqIndex() error = %v, wantErr %v", err, tt.wantErr) + return + } + t.Logf("got:%v\n", got) + }) + } +} diff --git a/service/record/answer/model/error.go b/service/record/answer/model/error.go new file mode 100644 index 0000000..0495f1d --- /dev/null +++ b/service/record/answer/model/error.go @@ -0,0 +1,18 @@ +package model + +import "errors" + +var ( + ErrAppId = errors.New("appId not compared") + ErrConsumeRedo = errors.New("process msg failed") + ErrCustomNotSupport = errors.New("biz not support") + + ErrGroupMemberNotExists = errors.New("group member not exists") +) + +//check error +var ( + ErrorEnvType = errors.New("unsupported event type") + ErrorChType = errors.New("unsupported channel type") + ErrorMsgType = errors.New("unsupported message type") +) diff --git a/service/record/answer/model/model.go b/service/record/answer/model/model.go new file mode 100644 index 0000000..b1b3905 --- /dev/null +++ b/service/record/answer/model/model.go @@ -0,0 +1,8 @@ +package model + +type MsgIndex struct { + Mid string + Seq string + SenderId string + CreateTime uint64 +} diff --git a/service/record/answer/server/grpc/body.go b/service/record/answer/server/grpc/body.go new file mode 100644 index 0000000..afeacb5 --- /dev/null +++ b/service/record/answer/server/grpc/body.go @@ -0,0 +1,69 @@ +package grpc + +import ( + "github.com/golang/protobuf/proto" + comet "gitlab.33.cn/chat/im/api/comet/grpc" + xproto "gitlab.33.cn/chat/imparse/proto" +) + +func noticeMsgData(channelType int32, from, target string, seq string, data []byte) ([]byte, error) { + var p comet.Proto + var err error + p.Op = int32(comet.Op_SendMsg) + p.Ver = 1 + p.Seq = 0 + + eventProto := &xproto.Proto{ + EventType: xproto.Proto_common, + } + comm := &xproto.Common{ + ChannelType: xproto.Channel(channelType), + Seq: seq, + From: from, + Target: target, + MsgType: xproto.MsgType_Notice, + Msg: data, + } + eventProto.Body, err = proto.Marshal(comm) + if err != nil { + return nil, err + } + p.Body, err = proto.Marshal(eventProto) + if err != nil { + return nil, err + } + bytes, err := proto.Marshal(&p) + if err != nil { + return nil, err + } + return bytes, nil +} + +func signalBody(target string, tp xproto.SignalType, actionData []byte) ([]byte, error) { + var p comet.Proto + var err error + p.Op = int32(comet.Op_SendMsg) + p.Ver = 1 + p.Seq = 0 + + eventProto := &xproto.Proto{ + EventType: xproto.Proto_Signal, + } + noticeProto := &xproto.Signal{ + Type: tp, + } + noticeProto.Body = actionData + eventProto.Body, err = proto.Marshal(noticeProto) + if err != nil { + return nil, err + } + p.Body, err = proto.Marshal(eventProto) + if err != nil { + return nil, err + } + bytes, err := proto.Marshal(&p) + if err != nil { + return nil, err + } + return bytes, nil +} diff --git a/service/record/answer/server/grpc/server.go b/service/record/answer/server/grpc/server.go new file mode 100644 index 0000000..16bde28 --- /dev/null +++ b/service/record/answer/server/grpc/server.go @@ -0,0 +1,109 @@ +package grpc + +import ( + "context" + "time" + + "github.com/opentracing/opentracing-go" + "github.com/rs/zerolog" + "github.com/uber/jaeger-client-go" + xgrpc "gitlab.33.cn/chat/dtalk/pkg/net/grpc" + pb "gitlab.33.cn/chat/dtalk/service/record/answer/api" + "gitlab.33.cn/chat/dtalk/service/record/answer/service" + "gitlab.33.cn/chat/im-pkg/trace" + "gitlab.33.cn/chat/imparse" + "google.golang.org/grpc" +) + +func New(c *xgrpc.ServerConfig, svr *service.Service, logger zerolog.Logger) *xgrpc.Server { + connectionTimeout := grpc.ConnectionTimeout(time.Duration(c.Timeout)) + ws := xgrpc.NewServer( + c, + connectionTimeout, + grpc.ChainUnaryInterceptor( + trace.OpentracingServerInterceptor, + OpentracingServerLogXInterceptor(logger, trace.TraceIDLogKey), + ), + ) + pb.RegisterAnswerServer(ws.Server(), &server{pb.UnimplementedAnswerServer{}, svr}) + ws, err := ws.Start() + if err != nil { + panic(err) + } + return ws +} + +func OpentracingServerLogXInterceptor(logger zerolog.Logger, fieldKey string) grpc.UnaryServerInterceptor { + return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { + span := opentracing.SpanFromContext(ctx) + var traceID string + if jgSpan, ok := span.Context().(jaeger.SpanContext); ok { + traceID = jgSpan.TraceID().String() + } + logger.UpdateContext(func(c zerolog.Context) zerolog.Context { + return c.Str(fieldKey, traceID) + }) + logger.UpdateContext(func(c zerolog.Context) zerolog.Context { + return c.Str("method", info.FullMethod) + }) + return handler(ctx, req) + } +} + +type server struct { + pb.UnimplementedAnswerServer + svr *service.Service +} + +func (s *server) PushCommonMsg(ctx context.Context, req *pb.PushCommonMsgReq) (*pb.PushCommonMsgReply, error) { + mid, createTime, err := s.svr.Push(ctx, req.GetKey(), req.GetFrom(), req.GetBody()) + if err != nil { + return &pb.PushCommonMsgReply{}, err + } + return &pb.PushCommonMsgReply{ + Mid: mid, + Time: createTime, + }, nil +} + +func (s *server) PushNoticeMsg(ctx context.Context, req *pb.PushNoticeMsgReq) (*pb.PushNoticeMsgReply, error) { + data, err := noticeMsgData(req.ChannelType, req.From, req.Target, req.Seq, req.Data) + if err != nil { + return &pb.PushNoticeMsgReply{}, err + } + mid, err := s.svr.InnerPush(ctx, "", req.From, req.Target, imparse.Undefined, data) + if err != nil { + return &pb.PushNoticeMsgReply{}, err + } + return &pb.PushNoticeMsgReply{ + Mid: mid, + }, nil +} + +func (s *server) UniCastSignal(ctx context.Context, req *pb.UniCastSignalReq) (*pb.UniCastSignalReply, error) { + data, err := signalBody(req.GetTarget(), req.GetType(), req.GetBody()) + if err != nil { + return &pb.UniCastSignalReply{}, err + } + mid, err := s.svr.InnerPush(ctx, "", "", req.GetTarget(), imparse.UniCast, data) + if err != nil { + return &pb.UniCastSignalReply{}, err + } + return &pb.UniCastSignalReply{ + Mid: mid, + }, nil +} + +func (s *server) GroupCastSignal(ctx context.Context, req *pb.GroupCastSignalReq) (*pb.GroupCastSignalReply, error) { + data, err := signalBody(req.GetTarget(), req.GetType(), req.GetBody()) + if err != nil { + return &pb.GroupCastSignalReply{}, err + } + mid, err := s.svr.InnerPush(ctx, "", "", req.GetTarget(), imparse.GroupCast, data) + if err != nil { + return &pb.GroupCastSignalReply{}, err + } + return &pb.GroupCastSignalReply{ + Mid: mid, + }, nil +} diff --git a/service/record/answer/service/check.go b/service/record/answer/service/check.go new file mode 100644 index 0000000..9bfaec4 --- /dev/null +++ b/service/record/answer/service/check.go @@ -0,0 +1,81 @@ +package service + +import ( + "gitlab.33.cn/chat/dtalk/service/record/answer/model" + "gitlab.33.cn/chat/imparse" + "gitlab.33.cn/chat/imparse/chat" + xproto "gitlab.33.cn/chat/imparse/proto" +) + +func isChTypeOk(t xproto.Channel) bool { + switch t { + case xproto.Channel_ToUser: + return true + case xproto.Channel_ToGroup: + return true + } + return false +} + +func isMsgTypeOk(t xproto.MsgType) bool { + switch t { + case xproto.MsgType_System: + return false + case xproto.MsgType_Text: + return true + case xproto.MsgType_Audio: + return true + case xproto.MsgType_Image: + return true + case xproto.MsgType_Video: + return true + case xproto.MsgType_File: + return true + case xproto.MsgType_Card: + return true + case xproto.MsgType_Notice: + return false + case xproto.MsgType_Forward: + return true + case xproto.MsgType_Transfer: + return true + case xproto.MsgType_Collect: + return false + case xproto.MsgType_RedPacket: + return true + case xproto.MsgType_ContactCard: + return true + } + return false +} + +var checker Checker + +type Checker struct { +} + +func (c *Checker) CheckFrame(frame imparse.Frame) error { + switch frame.Type() { + case chat.PrivateFrameType: + f := frame.(*chat.PrivateFrame) + if !isChTypeOk(f.GetChannelType()) { + return model.ErrorChType + } + if !isMsgTypeOk(f.GetMsgType()) { + return model.ErrorMsgType + } + case chat.GroupFrameType: + f := frame.(*chat.GroupFrame) + if !isChTypeOk(f.GetChannelType()) { + return model.ErrorChType + } + if !isMsgTypeOk(f.GetMsgType()) { + return model.ErrorMsgType + } + case chat.SignalFrameType: + return model.ErrorEnvType + default: + return model.ErrorEnvType + } + return nil +} diff --git a/service/record/answer/service/msg.go b/service/record/answer/service/msg.go new file mode 100644 index 0000000..2752859 --- /dev/null +++ b/service/record/answer/service/msg.go @@ -0,0 +1,133 @@ +package service + +import ( + "context" + "github.com/golang/protobuf/proto" + "gitlab.33.cn/chat/dtalk/pkg/util" + "gitlab.33.cn/chat/dtalk/service/record/answer/dao" + "gitlab.33.cn/chat/dtalk/service/record/answer/model" + record "gitlab.33.cn/chat/dtalk/service/record/proto" + logic "gitlab.33.cn/chat/im/api/logic/grpc" + "gitlab.33.cn/chat/imparse" + "gitlab.33.cn/chat/imparse/chat" + xproto "gitlab.33.cn/chat/imparse/proto" +) + +type DB struct { + dao *dao.Dao +} + +func (d *DB) GetMsg(ctx context.Context, from, msgId string) (*imparse.MsgIndex, error) { + r, err := d.dao.GetRecordSeqIndex(from, msgId) + if err != nil { + return nil, err + } + if r == nil { + return nil, nil + } + return &imparse.MsgIndex{ + Mid: r.Mid, + Seq: r.Seq, + SenderId: r.SenderId, + CreateTime: r.CreateTime, + }, nil +} + +func (d *DB) AddMsg(ctx context.Context, uid string, m *imparse.MsgIndex) error { + return d.dao.AddRecordSeqIndex(uid, &model.MsgIndex{ + Mid: m.Mid, + Seq: m.Seq, + SenderId: m.SenderId, + CreateTime: m.CreateTime, + }) +} + +func (d *DB) GetMid(ctx context.Context) (id int64, err error) { + return d.dao.GetMid(ctx) +} + +func (d *DB) GetFilters() map[imparse.FrameType][]imparse.Filter { + //filters + return map[imparse.FrameType][]imparse.Filter{ + chat.GroupFrameType: []imparse.Filter{ + func(ctx context.Context, frame imparse.Frame) error { + f := frame.(*chat.GroupFrame) + //判断群聊拦截 + if f.GetMsgType() != xproto.MsgType_Notice { + if ok, err := d.dao.CheckInGroup(ctx, f.GetFrom(), util.ToInt64(f.GetTarget())); !ok { + if err != nil { + return err + } + return model.ErrGroupMemberNotExists + } + } + return nil + }, + }, + } +} + +//send msg callback +type Exec struct { + appId string + dao *dao.Dao + logicClient logic.LogicClient +} + +func (e *Exec) Transport(ctx context.Context, mid int64, key, from, target string, ch imparse.Channel, frameType imparse.FrameType, data []byte) error { + pushMsg := &record.PushMsg{ + AppId: e.appId, + FromId: from, + Mid: mid, + Key: key, + Target: target, + Msg: data, + Type: int32(ch), + FrameType: string(frameType), + } + b, err := proto.Marshal(pushMsg) + if err != nil { + return err + } + return e.dao.PublishToSend(ctx, from, b) +} + +func (e *Exec) RevAck(ctx context.Context, id int64, keys []string, data []byte) error { + keysMsg := &logic.KeysMsg{ + AppId: e.appId, + ToKeys: keys, + Msg: data, + } + + _, err := e.logicClient.PushByKeys(ctx, keysMsg) + return err +} + +//send msg callback +type withoutAckExec struct { + appId string + dao *dao.Dao + logicClient logic.LogicClient +} + +func (e *withoutAckExec) Transport(ctx context.Context, mid int64, key, from, target string, ch imparse.Channel, frameType imparse.FrameType, data []byte) error { + pushMsg := &record.PushMsg{ + AppId: e.appId, + FromId: from, + Mid: mid, + Key: key, + Target: target, + Msg: data, + Type: int32(ch), + FrameType: string(frameType), + } + b, err := proto.Marshal(pushMsg) + if err != nil { + return err + } + return e.dao.PublishToSend(ctx, from, b) +} + +func (e *withoutAckExec) RevAck(ctx context.Context, id int64, keys []string, data []byte) error { + return nil +} diff --git a/service/record/answer/service/push.go b/service/record/answer/service/push.go new file mode 100644 index 0000000..cab09ce --- /dev/null +++ b/service/record/answer/service/push.go @@ -0,0 +1,74 @@ +package service + +import ( + "bytes" + "context" + "github.com/rs/zerolog" + "gitlab.33.cn/chat/imparse" + "gitlab.33.cn/chat/imparse/chat" +) + +func (s *Service) Push(ctx context.Context, key, from string, body []byte) (int64, uint64, error) { + log := zerolog.Ctx(ctx).With().Str("Uid", from).Str("ConnId", key).Logger() + + log.Debug().Msg("start create frame") + frame, err := s.parser.NewFrame(key, from, bytes.NewReader(body)) + if err != nil { + log.Error().Stack().Err(err).Msg("NewFrame error") + return 0, 0, err + } + log.Debug().Msg("start check frame") + if err := s.rcpAnswer.Check(ctx, &checker, frame); err != nil { + log.Error().Stack().Err(err).Msg("Check error") + return 0, 0, err + } + log.Debug().Msg("start frame filter") + createTime, err := s.rcpAnswer.Filter(ctx, frame) + if err != nil { + log.Error().Stack().Err(err).Msg("Filter error") + return 0, 0, err + } + log.Debug().Msg("start frame transport") + if err := s.rcpAnswer.Transport(ctx, frame); err != nil { + log.Error().Stack().Err(err).Msg("Transport error") + return 0, 0, err + } + log.Debug().Msg("start frame ack") + mid, err := s.rcpAnswer.Ack(ctx, frame) + if err != nil { + log.Error().Stack().Err(err).Msg("Ack error") + return 0, 0, err + } + log.Debug().Msg("deal msg send success") + return mid, createTime, nil +} + +//gRPC调用的内部推送通道,不经过check +func (s *Service) InnerPush(ctx context.Context, key, from, target string, pushType imparse.Channel, body []byte) (int64, error) { + log := zerolog.Ctx(ctx).With().Str("Uid", from).Str("ConnId", key).Str("Push Type", pushType.String()).Logger() + + log.Debug().Msg("start create frame") + frame, err := s.parser.NewFrame(key, from, bytes.NewReader(body), chat.WithTarget(target), chat.WithTransmissionMethod(pushType)) + if err != nil { + log.Error().Stack().Err(err).Msg("NewFrame error") + return 0, err + } + + log.Debug().Msg("start frame filter") + if _, err := s.rcpAnswer.Filter(ctx, frame); err != nil { + log.Error().Stack().Err(err).Msg("Filter error") + return 0, err + } + log.Debug().Msg("start frame transport") + if err := s.rcpAnswer.Transport(ctx, frame); err != nil { + log.Error().Stack().Err(err).Msg("Transport error") + return 0, err + } + log.Debug().Msg("start frame ack") + mid, err := s.rcpAnswer.Ack(ctx, frame) + if err != nil { + log.Error().Stack().Err(err).Msg("Ack error") + return 0, err + } + return mid, nil +} diff --git a/service/record/answer/service/push_test.go b/service/record/answer/service/push_test.go new file mode 100644 index 0000000..0402dac --- /dev/null +++ b/service/record/answer/service/push_test.go @@ -0,0 +1,25 @@ +package service + +import ( + "encoding/hex" + "testing" + + "gitlab.33.cn/chat/dtalk/service/record/answer/config" +) + +func TestService_Push(t *testing.T) { + config.Conf = config.Default() + svc := New(config.Conf) + + body, err := hex.DecodeString("1297011a2463653238663235312d326137612d346163312d613335352d34616462616436303866313222223134563767384141396e78523461347479737a683974344b766254585373504864712a2131424b4d35776d6b69596e4b4e6b72724839764259624e734c516b71737147563130013a1f8f8658c7f0fa4ebfb9713e76eeb1beedafc0d6356bdcffbc135d892b30d9e7409dc9aeb9a52f") + if err != nil { + t.Error(err) + return + } + got, got1, err := svc.Push("", "14V7g8AA9nxR4a4tyszh9t4KvbTXSsPHdq", body) + if err != nil { + t.Error(err) + return + } + t.Log(got, got1) +} diff --git a/service/record/answer/service/service.go b/service/record/answer/service/service.go new file mode 100644 index 0000000..58ff0a3 --- /dev/null +++ b/service/record/answer/service/service.go @@ -0,0 +1,223 @@ +package service + +import ( + "bytes" + "context" + "fmt" + "reflect" + "runtime" + "time" + + "github.com/Shopify/sarama" + "github.com/gammazero/workerpool" + "github.com/golang/protobuf/proto" + "github.com/opentracing/opentracing-go" + traceLog "github.com/opentracing/opentracing-go/log" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "github.com/uber/jaeger-client-go" + "gitlab.33.cn/chat/dtalk/pkg/naming" + "gitlab.33.cn/chat/dtalk/pkg/net/grpc" + "gitlab.33.cn/chat/dtalk/service/record/answer/config" + "gitlab.33.cn/chat/dtalk/service/record/answer/dao" + "gitlab.33.cn/chat/dtalk/service/record/answer/model" + "gitlab.33.cn/chat/dtalk/service/record/kafka/consumer" + "gitlab.33.cn/chat/im-pkg/trace" + comet "gitlab.33.cn/chat/im/api/comet/grpc" + logic "gitlab.33.cn/chat/im/api/logic/grpc" + "gitlab.33.cn/chat/imparse" + "gitlab.33.cn/chat/imparse/chat" + "google.golang.org/grpc/resolver" +) + +type Service struct { + cfg *config.Config + log zerolog.Logger + dao *dao.Dao + consumers map[string]*consumer.Consumer + logicClient logic.LogicClient + + answer imparse.Answer + rcpAnswer imparse.Answer + parser chat.StandardParse + + workPool *workerpool.WorkerPool +} + +func New(c *config.Config) *Service { + s := &Service{ + cfg: c, + log: log.Logger, + dao: dao.New(c), + consumers: consumer.NewKafkaConsumers( + fmt.Sprintf("goim-%s-topic", c.AppId), + fmt.Sprintf("goim-%s-group", c.AppId), c.MQSub.Brokers, c.MQSub.Number), + logicClient: newLogicClient(c), + workPool: workerpool.New(c.MQSub.MaxWorker), + } + db := DB{ + dao: s.dao, + } + exec := Exec{ + appId: s.cfg.AppId, + dao: s.dao, + logicClient: s.logicClient, + } + withoutAckExec := withoutAckExec{ + appId: s.cfg.AppId, + dao: s.dao, + logicClient: s.logicClient, + } + trace := &Trace{} + s.answer = imparse.NewStandardAnswer(&db, &exec, trace, db.GetFilters()) + s.rcpAnswer = imparse.NewStandardAnswer(&db, &withoutAckExec, trace, db.GetFilters()) + return s +} + +func newLogicClient(cfg *config.Config) logic.LogicClient { + rb := naming.NewResolver(cfg.LogicRPCClient.RegAddrs, cfg.LogicRPCClient.Schema) + resolver.Register(rb) + + addr := fmt.Sprintf("%s:///%s", cfg.LogicRPCClient.Schema, cfg.LogicRPCClient.SrvName) // "schema://[authority]/service" + fmt.Println("logic rpc client call addr:", addr) + + conn, err := grpc.NewGRPCConn(addr, time.Duration(cfg.LogicRPCClient.Dial)) + if err != nil { + panic(err) + } + return logic.NewLogicClient(conn) +} + +func (s *Service) Shutdown(ctx context.Context) { + down := make(chan struct{}) + go func() { + s.workPool.StopWait() + close(down) + }() + + select { + case <-ctx.Done(): + return + case <-down: + return + } +} + +func (s *Service) ListenMQ() { + s.log.Info().Int("numbers", len(s.consumers)).Msg("start serve mq consumers") + for _, c := range s.consumers { + go c.Listen(s.asyncRev) + } +} + +func (s *Service) asyncRev(msg *sarama.ConsumerMessage) error { + s.workPool.Submit(func() { + //init trace and log + s.log.Debug().Str("topic", msg.Topic). + Bytes("key", msg.Key). + Int32("partition", msg.Partition). + Int64("offset", msg.Offset). + Msg(fmt.Sprintf("consume %s", msg.Topic)) + err := s.consume(msg) + if err != nil { + //s.log.Error().Err(err).Msg("asyncRev consume error") + return + } + }) + return nil +} + +func (s *Service) consume(msg *sarama.ConsumerMessage) error { + bizMsg := new(logic.BizMsg) + if err := proto.Unmarshal(msg.Value, bizMsg); err != nil { + s.log.Error().Err(err).Bytes("bizMsg byte", msg.Value).Msg("logic.BizMsg proto.Unmarshal error") + return err + } + if bizMsg.AppId != s.cfg.AppId { + return model.ErrAppId + } + switch bizMsg.GetOp() { + case int32(comet.Op_SendMsg): + if err := s.consumeTraceAndLogIpt(msg, bizMsg, s.DealOpSendMsg); err != nil { + //TODO redo consume message + return err + } + default: + return model.ErrCustomNotSupport + } + return nil +} + +func (s *Service) consumeTraceAndLogIpt(msg *sarama.ConsumerMessage, bizMsg *logic.BizMsg, handler func(ctx context.Context, m *logic.BizMsg) error) error { + // trace + tracer := opentracing.GlobalTracer() + spanContext, err := trace.ExtractMQHeader(tracer, msg) + if err != nil { + // maybe rpc client not transform trace instance, it should be work continue + } + spanName := fmt.Sprintf("consume %s:%s", msg.Topic, comet.Op_SendMsg.String()) + span := tracer.StartSpan(spanName, opentracing.ChildOf(spanContext)) + defer span.Finish() + + traceId := "" + if jgSpan, ok := span.Context().(jaeger.SpanContext); ok { + traceId = jgSpan.TraceID().String() + } + //get global log instance + logger := s.log.With().Str(trace.TraceIDLogKey, traceId).Logger() + span.LogFields( + traceLog.String("AppId", bizMsg.GetAppId()), + traceLog.String("Uid", bizMsg.GetFromId()), + traceLog.String("ConnId", bizMsg.GetKey()), + traceLog.String("Option", comet.Op(bizMsg.GetOp()).String()), + ) + ctx := opentracing.ContextWithSpan(context.Background(), span) + ctx = logger.WithContext(ctx) + + err = handler(ctx, bizMsg) + if err != nil { + pointer := reflect.ValueOf(handler).Pointer() + funcName := runtime.FuncForPC(pointer).Name() + log.Error().Err(err).Msg(fmt.Sprintf("consume %s error", funcName)) + span.SetTag("ERROR", err) + return err + } + return nil +} + +func (s *Service) DealOpSendMsg(ctx context.Context, m *logic.BizMsg) error { + logger := zerolog.Ctx(ctx).With().Str("AppId", m.GetAppId()).Str("Uid", m.GetFromId()).Str("ConnId", m.GetKey()).Logger() + + logger.Debug().Msg("start create frame") + frame, err := s.parser.NewFrame(m.GetKey(), m.GetFromId(), bytes.NewReader(m.GetMsg())) + if err != nil { + logger.Error().Stack().Err(err).Msg("NewFrame error") + return err + } + logger.Debug().Msg("start check frame") + if err := s.answer.Check(ctx, &checker, frame); err != nil { + logger.Error().Stack().Err(err).Msg("Check error") + return err + } + logger.Debug().Msg("start frame filter") + _, err = s.answer.Filter(ctx, frame) + if err != nil { + logger.Error().Stack().Err(err).Msg("Filter error") + return err + } + logger.Debug().Msg("start frame transport") + err = s.answer.Transport(ctx, frame) + if err != nil { + logger.Error().Stack().Err(err).Msg("Transport error") + return err + } + // ACk A + logger.Debug().Msg("start frame ack") + _, err = s.answer.Ack(ctx, frame) + if err != nil { + logger.Error().Stack().Err(err).Msg("Ack error") + return err + } + logger.Debug().Msg("deal msg send success") + return nil +} diff --git a/service/record/answer/service/trace.go b/service/record/answer/service/trace.go new file mode 100644 index 0000000..3f03e57 --- /dev/null +++ b/service/record/answer/service/trace.go @@ -0,0 +1,16 @@ +package service + +import ( + "context" + "github.com/opentracing/opentracing-go" +) + +type Trace struct { +} + +func (t *Trace) StartSpanFromContext(ctx context.Context, funcName string) (func(), context.Context) { + span, ctx := opentracing.StartSpanFromContext(ctx, funcName) + return func() { + span.Finish() + }, ctx +} diff --git a/service/record/answer/version.go b/service/record/answer/version.go new file mode 100644 index 0000000..1f169ce --- /dev/null +++ b/service/record/answer/version.go @@ -0,0 +1,16 @@ +package pusher + +//var ( +// // The full version string +// Version = "0.6.3" +// +// // GitCommit is set with --ldflags "-X main.gitCommit=$(git rev-parse --short=8 HEAD)" +// GitCommit string +//) +// +//func GetVersion() string { +// if GitCommit != "" { +// return Version + "-" + GitCommit +// } +// return Version +//} diff --git a/service/record/kafka/consumer/consumer.go b/service/record/kafka/consumer/consumer.go new file mode 100644 index 0000000..8614e24 --- /dev/null +++ b/service/record/kafka/consumer/consumer.go @@ -0,0 +1,39 @@ +package consumer + +import ( + "github.com/Shopify/sarama" + cluster "github.com/bsm/sarama-cluster" + "github.com/rs/zerolog/log" +) + +type DealHandler func(msg *sarama.ConsumerMessage) error + +type Process interface { + Deal(msg *sarama.ConsumerMessage) error +} + +type Consumer struct { + *cluster.Consumer +} + +func (c *Consumer) Listen(deal DealHandler) { + for { + select { + case err := <-c.Errors(): + log.Error().Err(err).Msg("consumer error") + case n := <-c.Notifications(): + log.Info().Interface("number", n).Msg("consumer rebalanced") + case msg, ok := <-c.Messages(): + if !ok { + log.Debug().Str("topic", msg.Topic). + Int32("partition", msg.Partition). + Int64("offset", msg.Offset). + Bytes("key", msg.Key). + Msg("consume not ok") + return + } + deal(msg) + c.MarkOffset(msg, "") + } + } +} diff --git a/service/record/kafka/consumer/creator.go b/service/record/kafka/consumer/creator.go new file mode 100644 index 0000000..e48f914 --- /dev/null +++ b/service/record/kafka/consumer/creator.go @@ -0,0 +1,30 @@ +package consumer + +import ( + "strconv" + + "github.com/Shopify/sarama" + cluster "github.com/bsm/sarama-cluster" +) + +func NewKafkaConsumers(topic, group string, brokers []string, number uint32) map[string]*Consumer { + store := make(map[string]*Consumer) + num := int(number) + for i := 0; i < num; i++ { + store[strconv.Itoa(i)] = &Consumer{Consumer: newKafkaSub(topic, group, brokers)} + } + return store +} + +func newKafkaSub(topic, group string, brokers []string) *cluster.Consumer { + c := cluster.NewConfig() + c.Consumer.Return.Errors = true + c.Group.Return.Notifications = true + c.Version = sarama.V0_11_0_2 + + consumer, err := cluster.NewConsumer(brokers, group, []string{topic}, c) + if err != nil { + panic(err) + } + return consumer +} diff --git a/service/record/kafka/consumer/mutil-group/main.go b/service/record/kafka/consumer/mutil-group/main.go new file mode 100644 index 0000000..201718e --- /dev/null +++ b/service/record/kafka/consumer/mutil-group/main.go @@ -0,0 +1,80 @@ +package main + +import ( + "flag" + "fmt" + "os" + "os/signal" + "syscall" + "time" + + "github.com/Shopify/sarama" + cluster "github.com/bsm/sarama-cluster" + log "github.com/inconshreveable/log15" +) + +var ( + group string +) + +func init() { + flag.StringVar(&group, "g", "test-group", "") +} + +func main() { + flag.Parse() + fmt.Println(group) + go Listen(newKafkaSub()) + // init signal + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT) + for { + s := <-c + switch s { + case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT: + time.Sleep(time.Second * 2) + return + case syscall.SIGHUP: + // TODO reload + default: + return + } + } +} + +func deal(msg *sarama.ConsumerMessage) error { + fmt.Println(msg) + fmt.Println(msg.Value) + return nil +} + +func Listen(c *cluster.Consumer) { + for { + select { + case err := <-c.Errors(): + log.Error("consumer error", "err", err) + case n := <-c.Notifications(): + log.Info("consumer rebalanced", "number", n) + case msg, ok := <-c.Messages(): + if !ok { + log.Debug("consume not ok", "topic", msg.Topic, "partition", msg.Partition, "offset", msg.Offset, "key", msg.Key) + return + } + deal(msg) + c.MarkOffset(msg, "") + } + } +} + +func newKafkaSub() *cluster.Consumer { + c := cluster.NewConfig() + c.Consumer.Return.Errors = true + c.Group.Return.Notifications = true + + topic := "test-topic" + consumer, err := cluster.NewConsumer([]string{"127.0.0.1:9092"}, group, []string{topic}, c) + if err != nil { + panic(err) + } + return consumer +} diff --git a/service/record/kafka/consumer/test/main.go b/service/record/kafka/consumer/test/main.go new file mode 100644 index 0000000..6584337 --- /dev/null +++ b/service/record/kafka/consumer/test/main.go @@ -0,0 +1,73 @@ +package main + +import ( + "fmt" + cluster "github.com/bsm/sarama-cluster" + "github.com/inconshreveable/log15" + "gitlab.33.cn/chat/dtalk/service/offline-push/service/kafka" + "gitlab.33.cn/chat/dtalk/service/record/kafka/consumer" + logic "gitlab.33.cn/chat/im/api/logic/grpc" + "os" + "os/signal" + "strconv" + "syscall" + "time" +) + +var log = log15.New("test main", "model", "kafka consume") + +func main() { + store := newKafkaConsumers() + p := &process{} + for i, c := range store { + log.Debug(fmt.Sprintf("accept %v", i)) + go c.Listen(p) + } + + // init signal + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT) + for { + s := <-c + switch s { + case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT: + time.Sleep(time.Second * 2) + return + case syscall.SIGHUP: + // TODO reload + default: + return + } + } +} + +func newKafkaSub() *cluster.Consumer { + c := cluster.NewConfig() + c.Consumer.Return.Errors = true + c.Group.Return.Notifications = true + + topic := fmt.Sprintf("goim-%s-topic", "dtalk") + group := fmt.Sprintf("goim-%s-group", "dtalk") + consumer, err := cluster.NewConsumer([]string{"127.0.0.1:9092"}, group, []string{topic}, c) + if err != nil { + panic(err) + } + return consumer +} + +func newKafkaConsumers() map[string]*kafka.Consumer { + store := make(map[string]*kafka.Consumer) + num := 1 + for i := 0; i < num; i++ { + store[strconv.Itoa(i)] = &kafka.Consumer{Consumer: newKafkaSub()} + } + return store +} + +type process struct { +} + +func (p *process) Deal(m *logic.BizMsg) error { + fmt.Println(m) + return nil +} diff --git a/service/record/kafka/consumer/test/mqtest b/service/record/kafka/consumer/test/mqtest new file mode 100644 index 0000000..6c425a9 Binary files /dev/null and b/service/record/kafka/consumer/test/mqtest differ diff --git a/service/record/kafka/publisher/publisher.go b/service/record/kafka/publisher/publisher.go new file mode 100644 index 0000000..ead3362 --- /dev/null +++ b/service/record/kafka/publisher/publisher.go @@ -0,0 +1,17 @@ +package publisher + +import ( + kafka "gopkg.in/Shopify/sarama.v1" +) + +func NewKafkaPub(brokers []string) kafka.SyncProducer { + kc := kafka.NewConfig() + kc.Producer.RequiredAcks = kafka.WaitForAll // Wait for all in-sync replicas to ack the message + kc.Producer.Retry.Max = 10 // Retry up to 10 times to produce the message + kc.Producer.Return.Successes = true + pub, err := kafka.NewSyncProducer(brokers, kc) + if err != nil { + panic(err) + } + return pub +} diff --git a/service/record/kafka/publisher/publisher_test.go b/service/record/kafka/publisher/publisher_test.go new file mode 100644 index 0000000..88f5aeb --- /dev/null +++ b/service/record/kafka/publisher/publisher_test.go @@ -0,0 +1,24 @@ +package publisher + +import ( + "github.com/google/uuid" + "testing" + + "gopkg.in/Shopify/sarama.v1" +) + +func TestNewKafkaPub(t *testing.T) { + pub := NewKafkaPub([]string{"127.0.0.1:9092"}) + appTopic := "test-topic" + + data := "1" + m := &sarama.ProducerMessage{ + Key: sarama.StringEncoder(uuid.New().String()), + Topic: appTopic, + Value: sarama.ByteEncoder(data), + } + if _, _, err := pub.SendMessage(m); err != nil { + t.Error("SendMessage", err) + } + t.Log("success") +} diff --git a/service/record/proto/create.sh b/service/record/proto/create.sh new file mode 100644 index 0000000..0e5dbf9 --- /dev/null +++ b/service/record/proto/create.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +protoc -I. -I$GOPATH/src \ + --go_out=. --go_opt=paths=source_relative \ + --go-grpc_out=. --go-grpc_opt=paths=source_relative \ + *.proto \ No newline at end of file diff --git a/service/record/proto/record.pb.go b/service/record/proto/record.pb.go new file mode 100644 index 0000000..3e66627 --- /dev/null +++ b/service/record/proto/record.pb.go @@ -0,0 +1,509 @@ +// protoc -I=. -I=$GOPATH/src --go_out=plugins=grpc:. *.proto + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc v3.19.1 +// source: record.proto + +package record + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Operation int32 + +const ( + Operation_BatchPush Operation = 0 + Operation_MarkRead Operation = 1 + Operation_StoreMsg Operation = 2 + Operation_SyncMsg Operation = 3 +) + +// Enum value maps for Operation. +var ( + Operation_name = map[int32]string{ + 0: "BatchPush", + 1: "MarkRead", + 2: "StoreMsg", + 3: "SyncMsg", + } + Operation_value = map[string]int32{ + "BatchPush": 0, + "MarkRead": 1, + "StoreMsg": 2, + "SyncMsg": 3, + } +) + +func (x Operation) Enum() *Operation { + p := new(Operation) + *p = x + return p +} + +func (x Operation) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Operation) Descriptor() protoreflect.EnumDescriptor { + return file_record_proto_enumTypes[0].Descriptor() +} + +func (Operation) Type() protoreflect.EnumType { + return &file_record_proto_enumTypes[0] +} + +func (x Operation) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Operation.Descriptor instead. +func (Operation) EnumDescriptor() ([]byte, []int) { + return file_record_proto_rawDescGZIP(), []int{0} +} + +// record --> mq +type PushMsg struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + AppId string `protobuf:"bytes,1,opt,name=appId,proto3" json:"appId,omitempty"` + FromId string `protobuf:"bytes,2,opt,name=fromId,proto3" json:"fromId,omitempty"` + Mid int64 `protobuf:"varint,3,opt,name=mid,proto3" json:"mid,omitempty"` + Key string `protobuf:"bytes,4,opt,name=key,proto3" json:"key,omitempty"` + Target string `protobuf:"bytes,5,opt,name=target,proto3" json:"target,omitempty"` //推送目标 用户id 或者 群id + Msg []byte `protobuf:"bytes,6,opt,name=msg,proto3" json:"msg,omitempty"` + Type int32 `protobuf:"varint,7,opt,name=type,proto3" json:"type,omitempty"` + FrameType string `protobuf:"bytes,8,opt,name=frameType,proto3" json:"frameType,omitempty"` +} + +func (x *PushMsg) Reset() { + *x = PushMsg{} + if protoimpl.UnsafeEnabled { + mi := &file_record_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PushMsg) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PushMsg) ProtoMessage() {} + +func (x *PushMsg) ProtoReflect() protoreflect.Message { + mi := &file_record_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PushMsg.ProtoReflect.Descriptor instead. +func (*PushMsg) Descriptor() ([]byte, []int) { + return file_record_proto_rawDescGZIP(), []int{0} +} + +func (x *PushMsg) GetAppId() string { + if x != nil { + return x.AppId + } + return "" +} + +func (x *PushMsg) GetFromId() string { + if x != nil { + return x.FromId + } + return "" +} + +func (x *PushMsg) GetMid() int64 { + if x != nil { + return x.Mid + } + return 0 +} + +func (x *PushMsg) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *PushMsg) GetTarget() string { + if x != nil { + return x.Target + } + return "" +} + +func (x *PushMsg) GetMsg() []byte { + if x != nil { + return x.Msg + } + return nil +} + +func (x *PushMsg) GetType() int32 { + if x != nil { + return x.Type + } + return 0 +} + +func (x *PushMsg) GetFrameType() string { + if x != nil { + return x.FrameType + } + return "" +} + +// record --> mq +type RecordDeal struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + AppId string `protobuf:"bytes,1,opt,name=appId,proto3" json:"appId,omitempty"` + FromId string `protobuf:"bytes,2,opt,name=fromId,proto3" json:"fromId,omitempty"` + Key string `protobuf:"bytes,3,opt,name=key,proto3" json:"key,omitempty"` + Opt Operation `protobuf:"varint,4,opt,name=opt,proto3,enum=dtalk.record.Operation" json:"opt,omitempty"` + Msg []byte `protobuf:"bytes,5,opt,name=msg,proto3" json:"msg,omitempty"` +} + +func (x *RecordDeal) Reset() { + *x = RecordDeal{} + if protoimpl.UnsafeEnabled { + mi := &file_record_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RecordDeal) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RecordDeal) ProtoMessage() {} + +func (x *RecordDeal) ProtoReflect() protoreflect.Message { + mi := &file_record_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RecordDeal.ProtoReflect.Descriptor instead. +func (*RecordDeal) Descriptor() ([]byte, []int) { + return file_record_proto_rawDescGZIP(), []int{1} +} + +func (x *RecordDeal) GetAppId() string { + if x != nil { + return x.AppId + } + return "" +} + +func (x *RecordDeal) GetFromId() string { + if x != nil { + return x.FromId + } + return "" +} + +func (x *RecordDeal) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *RecordDeal) GetOpt() Operation { + if x != nil { + return x.Opt + } + return Operation_BatchPush +} + +func (x *RecordDeal) GetMsg() []byte { + if x != nil { + return x.Msg + } + return nil +} + +//Operation=MarkRead +type Marked struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` + Mids []int64 `protobuf:"varint,2,rep,packed,name=mids,proto3" json:"mids,omitempty"` +} + +func (x *Marked) Reset() { + *x = Marked{} + if protoimpl.UnsafeEnabled { + mi := &file_record_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Marked) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Marked) ProtoMessage() {} + +func (x *Marked) ProtoReflect() protoreflect.Message { + mi := &file_record_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Marked.ProtoReflect.Descriptor instead. +func (*Marked) Descriptor() ([]byte, []int) { + return file_record_proto_rawDescGZIP(), []int{2} +} + +func (x *Marked) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *Marked) GetMids() []int64 { + if x != nil { + return x.Mids + } + return nil +} + +//Operation=SyncMsg +type Sync struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Mid int64 `protobuf:"varint,1,opt,name=mid,proto3" json:"mid,omitempty"` +} + +func (x *Sync) Reset() { + *x = Sync{} + if protoimpl.UnsafeEnabled { + mi := &file_record_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Sync) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Sync) ProtoMessage() {} + +func (x *Sync) ProtoReflect() protoreflect.Message { + mi := &file_record_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Sync.ProtoReflect.Descriptor instead. +func (*Sync) Descriptor() ([]byte, []int) { + return file_record_proto_rawDescGZIP(), []int{3} +} + +func (x *Sync) GetMid() int64 { + if x != nil { + return x.Mid + } + return 0 +} + +var File_record_proto protoreflect.FileDescriptor + +var file_record_proto_rawDesc = []byte{ + 0x0a, 0x0c, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0c, + 0x64, 0x74, 0x61, 0x6c, 0x6b, 0x2e, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x22, 0xb7, 0x01, 0x0a, + 0x07, 0x50, 0x75, 0x73, 0x68, 0x4d, 0x73, 0x67, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x70, 0x70, 0x49, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x70, 0x70, 0x49, 0x64, 0x12, 0x16, + 0x0a, 0x06, 0x66, 0x72, 0x6f, 0x6d, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x66, 0x72, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x69, 0x64, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x03, 0x6d, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, + 0x65, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x03, 0x6d, 0x73, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x66, 0x72, 0x61, 0x6d, + 0x65, 0x54, 0x79, 0x70, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x66, 0x72, 0x61, + 0x6d, 0x65, 0x54, 0x79, 0x70, 0x65, 0x22, 0x89, 0x01, 0x0a, 0x0a, 0x52, 0x65, 0x63, 0x6f, 0x72, + 0x64, 0x44, 0x65, 0x61, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x70, 0x70, 0x49, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x70, 0x70, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x66, + 0x72, 0x6f, 0x6d, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x72, 0x6f, + 0x6d, 0x49, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x03, 0x6f, 0x70, 0x74, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x64, 0x74, 0x61, 0x6c, 0x6b, 0x2e, 0x72, 0x65, 0x63, 0x6f, 0x72, + 0x64, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x03, 0x6f, 0x70, 0x74, + 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6d, + 0x73, 0x67, 0x22, 0x30, 0x0a, 0x06, 0x4d, 0x61, 0x72, 0x6b, 0x65, 0x64, 0x12, 0x12, 0x0a, 0x04, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, + 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x03, 0x52, 0x04, + 0x6d, 0x69, 0x64, 0x73, 0x22, 0x18, 0x0a, 0x04, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x10, 0x0a, 0x03, + 0x6d, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x6d, 0x69, 0x64, 0x2a, 0x43, + 0x0a, 0x09, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0d, 0x0a, 0x09, 0x42, + 0x61, 0x74, 0x63, 0x68, 0x50, 0x75, 0x73, 0x68, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x4d, 0x61, + 0x72, 0x6b, 0x52, 0x65, 0x61, 0x64, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x74, 0x6f, 0x72, + 0x65, 0x4d, 0x73, 0x67, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x79, 0x6e, 0x63, 0x4d, 0x73, + 0x67, 0x10, 0x03, 0x42, 0x28, 0x5a, 0x26, 0x67, 0x69, 0x74, 0x6c, 0x61, 0x62, 0x2e, 0x33, 0x33, + 0x2e, 0x63, 0x6e, 0x2f, 0x63, 0x68, 0x61, 0x74, 0x2f, 0x64, 0x74, 0x61, 0x6c, 0x6b, 0x2f, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_record_proto_rawDescOnce sync.Once + file_record_proto_rawDescData = file_record_proto_rawDesc +) + +func file_record_proto_rawDescGZIP() []byte { + file_record_proto_rawDescOnce.Do(func() { + file_record_proto_rawDescData = protoimpl.X.CompressGZIP(file_record_proto_rawDescData) + }) + return file_record_proto_rawDescData +} + +var file_record_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_record_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_record_proto_goTypes = []interface{}{ + (Operation)(0), // 0: dtalk.record.Operation + (*PushMsg)(nil), // 1: dtalk.record.PushMsg + (*RecordDeal)(nil), // 2: dtalk.record.RecordDeal + (*Marked)(nil), // 3: dtalk.record.Marked + (*Sync)(nil), // 4: dtalk.record.Sync +} +var file_record_proto_depIdxs = []int32{ + 0, // 0: dtalk.record.RecordDeal.opt:type_name -> dtalk.record.Operation + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_record_proto_init() } +func file_record_proto_init() { + if File_record_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_record_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PushMsg); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_record_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RecordDeal); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_record_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Marked); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_record_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Sync); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_record_proto_rawDesc, + NumEnums: 1, + NumMessages: 4, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_record_proto_goTypes, + DependencyIndexes: file_record_proto_depIdxs, + EnumInfos: file_record_proto_enumTypes, + MessageInfos: file_record_proto_msgTypes, + }.Build() + File_record_proto = out.File + file_record_proto_rawDesc = nil + file_record_proto_goTypes = nil + file_record_proto_depIdxs = nil +} diff --git a/service/record/proto/record.proto b/service/record/proto/record.proto new file mode 100644 index 0000000..9993fce --- /dev/null +++ b/service/record/proto/record.proto @@ -0,0 +1,44 @@ +// protoc -I=. -I=$GOPATH/src --go_out=plugins=grpc:. *.proto +syntax = "proto3"; + +package dtalk.record; +option go_package = "gitlab.33.cn/chat/dtalk/service/record"; + +// record --> mq +message PushMsg { + string appId = 1; + string fromId = 2; + int64 mid = 3; + string key = 4; + string target = 5; //推送目标 用户id 或者 群id + bytes msg = 6; + int32 type = 7; + string frameType = 8; +} + +enum Operation { + BatchPush = 0; + MarkRead = 1; + StoreMsg = 2; + SyncMsg = 3; +} + +// record --> mq +message RecordDeal { + string appId = 1; + string fromId = 2; + string key = 3; + Operation opt = 4; + bytes msg = 5; +} + +//Operation=MarkRead +message Marked { + string type = 1; + repeated int64 mids = 2; +} + +//Operation=SyncMsg +message Sync { + int64 mid = 1; +} diff --git a/service/record/pusher/CHANGELOG.md b/service/record/pusher/CHANGELOG.md new file mode 100644 index 0000000..15e6be9 --- /dev/null +++ b/service/record/pusher/CHANGELOG.md @@ -0,0 +1,39 @@ +版本号`major.minor.patch`具体规则如下: +- major:主版本号,如有重大版本重构则该字段递增,通常各主版本间接口不兼容。 +- minor:次版本号,各次版本号间接口保持兼容,如有接口新增或优化则该字段递增。 +- patch:补丁号,如有功能改善或缺陷修复则该字段递增。 + +## version 0.6.4 + +**Feature** +- 修改imparse本地依赖为远程仓库 2021_12_07_17_44_27 + + +## version 0.6.3 + +**配置文件更新** + +所有 `[xxxRPCClient]` 增加 `RegAddrs = "127.0.0.1:2379"` 字段 + +**Feature** +- 重构 pusher @v0.6.0-pre 2021.10.14 +- 更新 etcdv3.5.0 v0.6.2 +- 修复新用户连接时没有群聊也强行加入群聊的 bug v0.6.3 + + +## version 0.5 + +**Feature** +- 新增 3 中通知类型 @v0.5.2 +- 转账交易消息格式支持 @v0.5.3 2021.8.16 + + +## example x.x.x @yy.mm.dd + +**Feature** + +**Bug Fixes** + +**Improvement** + +**Breaking Change** diff --git a/service/record/pusher/Makefile b/service/record/pusher/Makefile new file mode 100644 index 0000000..391117f --- /dev/null +++ b/service/record/pusher/Makefile @@ -0,0 +1,43 @@ +# golang1.9 or latest +# 1. make help +# 2. make dep +# 3. make build +# ... + +VERSION := $(shell echo $(shell cat cmd/main.go | grep "projectVersion =" | cut -d '=' -f2)) +APP_NAME := pusher +BUILD_DIR := build +APP := ${BUILD_DIR}/${APP_NAME}_v${VERSION} +PKG_NAME := ${APP_NAME}_v${VERSION} +PKG := ${PKG_NAME}.tar.gz + +main_path = "main" +go_version = $(shell go version | awk '{ print $3 }') +build_time = $(shell date "+%Y-%m-%d %H:%M:%S %Z") +git_commit = $(shell git rev-parse --short=10 HEAD) +flags := -ldflags "-X '${main_path}.goVersion=${go_version}' \ +-X '${main_path}.buildTime=${build_time}' \ +-X '${main_path}.gitCommit=${git_commit}' \ +-X 'google.golang.org/protobuf/reflect/protoregistry.conflictPolicy=warn'" + +.PHONY: clean build pkg + +clean: ## Remove previous build + @rm -rf ${BUILD_DIR} + @go clean + +build: #checkgofmt ## Build the binary file + GOOS=linux GOARCH=amd64 GO111MODULE=on GOPROXY=https://goproxy.cn,direct GOSUMDB="sum.golang.google.cn" go build -v $(flags) -o $(APP) cmd/main.go + +pkg: build ## Package + mkdir -p ${PKG_NAME}/bin + mkdir -p ${PKG_NAME}/etc + cp ${APP} ${PKG_NAME}/bin/ + cp etc/* ${PKG_NAME}/etc/ + tar zvcf ${PKG} ${PKG_NAME} + rm -rf ${PKG_NAME} + +REMOTE_BIN_PATH := /opt/dtalk/srv/app/bin +upload: build + rsync -r $(APP) 107:$(REMOTE_BIN_PATH) + ssh 107 "cd $(REMOTE_BIN_PATH) && chmod 777 $(PKG_NAME) && ln -sf $(PKG_NAME) $(APP_NAME) && supervisorctl restart $(APP_NAME)" \ No newline at end of file diff --git a/service/record/pusher/api/api.pb.go b/service/record/pusher/api/api.pb.go new file mode 100644 index 0000000..d7bcce9 --- /dev/null +++ b/service/record/pusher/api/api.pb.go @@ -0,0 +1,260 @@ +// protoc -I=. -I=$GOPATH/src --go_out=plugins=grpc:. *.proto + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc v3.19.1 +// source: api.proto + +package pusher + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type PushReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + From string `protobuf:"bytes,2,opt,name=from,proto3" json:"from,omitempty"` + Mid string `protobuf:"bytes,3,opt,name=mid,proto3" json:"mid,omitempty"` + Target string `protobuf:"bytes,4,opt,name=target,proto3" json:"target,omitempty"` + Data []byte `protobuf:"bytes,5,opt,name=data,proto3" json:"data,omitempty"` + Type int32 `protobuf:"varint,6,opt,name=type,proto3" json:"type,omitempty"` + FrameType string `protobuf:"bytes,7,opt,name=frameType,proto3" json:"frameType,omitempty"` +} + +func (x *PushReq) Reset() { + *x = PushReq{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PushReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PushReq) ProtoMessage() {} + +func (x *PushReq) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PushReq.ProtoReflect.Descriptor instead. +func (*PushReq) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{0} +} + +func (x *PushReq) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *PushReq) GetFrom() string { + if x != nil { + return x.From + } + return "" +} + +func (x *PushReq) GetMid() string { + if x != nil { + return x.Mid + } + return "" +} + +func (x *PushReq) GetTarget() string { + if x != nil { + return x.Target + } + return "" +} + +func (x *PushReq) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +func (x *PushReq) GetType() int32 { + if x != nil { + return x.Type + } + return 0 +} + +func (x *PushReq) GetFrameType() string { + if x != nil { + return x.FrameType + } + return "" +} + +type PushReply struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *PushReply) Reset() { + *x = PushReply{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PushReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PushReply) ProtoMessage() {} + +func (x *PushReply) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PushReply.ProtoReflect.Descriptor instead. +func (*PushReply) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{1} +} + +var File_api_proto protoreflect.FileDescriptor + +var file_api_proto_rawDesc = []byte{ + 0x0a, 0x09, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0c, 0x64, 0x74, 0x61, + 0x6c, 0x6b, 0x2e, 0x70, 0x75, 0x73, 0x68, 0x65, 0x72, 0x22, 0x9f, 0x01, 0x0a, 0x07, 0x50, 0x75, + 0x73, 0x68, 0x52, 0x65, 0x71, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x10, 0x0a, 0x03, 0x6d, + 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x69, 0x64, 0x12, 0x16, 0x0a, + 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, + 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1c, 0x0a, + 0x09, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x54, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x54, 0x79, 0x70, 0x65, 0x22, 0x0b, 0x0a, 0x09, 0x50, + 0x75, 0x73, 0x68, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x32, 0x46, 0x0a, 0x06, 0x50, 0x75, 0x73, 0x68, + 0x65, 0x72, 0x12, 0x3c, 0x0a, 0x0a, 0x50, 0x75, 0x73, 0x68, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, + 0x12, 0x15, 0x2e, 0x64, 0x74, 0x61, 0x6c, 0x6b, 0x2e, 0x70, 0x75, 0x73, 0x68, 0x65, 0x72, 0x2e, + 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, 0x71, 0x1a, 0x17, 0x2e, 0x64, 0x74, 0x61, 0x6c, 0x6b, 0x2e, + 0x70, 0x75, 0x73, 0x68, 0x65, 0x72, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, 0x70, 0x6c, 0x79, + 0x42, 0x2f, 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x6c, 0x61, 0x62, 0x2e, 0x33, 0x33, 0x2e, 0x63, 0x6e, + 0x2f, 0x63, 0x68, 0x61, 0x74, 0x2f, 0x64, 0x74, 0x61, 0x6c, 0x6b, 0x2f, 0x73, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x2f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x2f, 0x70, 0x75, 0x73, 0x68, 0x65, + 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_api_proto_rawDescOnce sync.Once + file_api_proto_rawDescData = file_api_proto_rawDesc +) + +func file_api_proto_rawDescGZIP() []byte { + file_api_proto_rawDescOnce.Do(func() { + file_api_proto_rawDescData = protoimpl.X.CompressGZIP(file_api_proto_rawDescData) + }) + return file_api_proto_rawDescData +} + +var file_api_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_api_proto_goTypes = []interface{}{ + (*PushReq)(nil), // 0: dtalk.pusher.PushReq + (*PushReply)(nil), // 1: dtalk.pusher.PushReply +} +var file_api_proto_depIdxs = []int32{ + 0, // 0: dtalk.pusher.Pusher.PushClient:input_type -> dtalk.pusher.PushReq + 1, // 1: dtalk.pusher.Pusher.PushClient:output_type -> dtalk.pusher.PushReply + 1, // [1:2] is the sub-list for method output_type + 0, // [0:1] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_api_proto_init() } +func file_api_proto_init() { + if File_api_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_api_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PushReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PushReply); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_api_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_api_proto_goTypes, + DependencyIndexes: file_api_proto_depIdxs, + MessageInfos: file_api_proto_msgTypes, + }.Build() + File_api_proto = out.File + file_api_proto_rawDesc = nil + file_api_proto_goTypes = nil + file_api_proto_depIdxs = nil +} diff --git a/service/record/pusher/api/api.proto b/service/record/pusher/api/api.proto new file mode 100644 index 0000000..e2ed2c3 --- /dev/null +++ b/service/record/pusher/api/api.proto @@ -0,0 +1,22 @@ +// protoc -I=. -I=$GOPATH/src --go_out=plugins=grpc:. *.proto +syntax = "proto3"; + +package dtalk.pusher; +option go_package = "gitlab.33.cn/chat/dtalk/service/record/pusher"; + +message PushReq { + string key = 1; + string from = 2; + string mid = 3; + string target = 4; + bytes data = 5; + int32 type = 6; + string frameType = 7; +} + +message PushReply { +} + +service Pusher { + rpc PushClient(PushReq) returns (PushReply); +} diff --git a/service/record/pusher/api/api_grpc.pb.go b/service/record/pusher/api/api_grpc.pb.go new file mode 100644 index 0000000..df1df06 --- /dev/null +++ b/service/record/pusher/api/api_grpc.pb.go @@ -0,0 +1,101 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. + +package pusher + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// PusherClient is the client API for Pusher service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type PusherClient interface { + PushClient(ctx context.Context, in *PushReq, opts ...grpc.CallOption) (*PushReply, error) +} + +type pusherClient struct { + cc grpc.ClientConnInterface +} + +func NewPusherClient(cc grpc.ClientConnInterface) PusherClient { + return &pusherClient{cc} +} + +func (c *pusherClient) PushClient(ctx context.Context, in *PushReq, opts ...grpc.CallOption) (*PushReply, error) { + out := new(PushReply) + err := c.cc.Invoke(ctx, "/dtalk.pusher.Pusher/PushClient", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// PusherServer is the server API for Pusher service. +// All implementations must embed UnimplementedPusherServer +// for forward compatibility +type PusherServer interface { + PushClient(context.Context, *PushReq) (*PushReply, error) + mustEmbedUnimplementedPusherServer() +} + +// UnimplementedPusherServer must be embedded to have forward compatible implementations. +type UnimplementedPusherServer struct { +} + +func (UnimplementedPusherServer) PushClient(context.Context, *PushReq) (*PushReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method PushClient not implemented") +} +func (UnimplementedPusherServer) mustEmbedUnimplementedPusherServer() {} + +// UnsafePusherServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to PusherServer will +// result in compilation errors. +type UnsafePusherServer interface { + mustEmbedUnimplementedPusherServer() +} + +func RegisterPusherServer(s grpc.ServiceRegistrar, srv PusherServer) { + s.RegisterService(&Pusher_ServiceDesc, srv) +} + +func _Pusher_PushClient_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PushReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PusherServer).PushClient(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.pusher.Pusher/PushClient", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PusherServer).PushClient(ctx, req.(*PushReq)) + } + return interceptor(ctx, in, info, handler) +} + +// Pusher_ServiceDesc is the grpc.ServiceDesc for Pusher service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Pusher_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "dtalk.pusher.Pusher", + HandlerType: (*PusherServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "PushClient", + Handler: _Pusher_PushClient_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "api.proto", +} diff --git a/service/record/pusher/api/client.go b/service/record/pusher/api/client.go new file mode 100644 index 0000000..5133375 --- /dev/null +++ b/service/record/pusher/api/client.go @@ -0,0 +1,45 @@ +package pusher + +import ( + "context" + "fmt" + "time" + + "gitlab.33.cn/chat/dtalk/pkg/naming" + xgrpc "gitlab.33.cn/chat/dtalk/pkg/net/grpc" + "gitlab.33.cn/chat/im-pkg/trace" + "google.golang.org/grpc" + "google.golang.org/grpc/resolver" +) + +// AppID unique app id for service discovery +//const AppID = "identify.service.pusher" + +type Client struct { + client PusherClient +} + +func New(etcdAddr, schema, srvName string, dial time.Duration) *Client { + rb := naming.NewResolver(etcdAddr, schema) + resolver.Register(rb) + + addr := fmt.Sprintf("%s:///%s", schema, srvName) // "schema://[authority]/service" + fmt.Println("pusher rpc client call addr:", addr) + + conn, err := xgrpc.NewGRPCConnWithOpts(addr, dial, grpc.WithUnaryInterceptor(trace.OpentracingClientInterceptor)) + if err != nil { + panic(err) + } + return &Client{ + client: NewPusherClient(conn), + } +} + +func (c *Client) PushClient(ctx context.Context, key, from string, data []byte) error { + _, err := c.client.PushClient(ctx, &PushReq{ + Key: key, + From: from, + Data: data, + }) + return err +} diff --git a/service/record/pusher/api/create.sh b/service/record/pusher/api/create.sh new file mode 100644 index 0000000..0e5dbf9 --- /dev/null +++ b/service/record/pusher/api/create.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +protoc -I. -I$GOPATH/src \ + --go_out=. --go_opt=paths=source_relative \ + --go-grpc_out=. --go-grpc_opt=paths=source_relative \ + *.proto \ No newline at end of file diff --git a/service/record/pusher/cmd/main.go b/service/record/pusher/cmd/main.go new file mode 100644 index 0000000..1d30c61 --- /dev/null +++ b/service/record/pusher/cmd/main.go @@ -0,0 +1,135 @@ +package main + +import ( + "context" + "flag" + "fmt" + "net" + "os" + "os/signal" + "syscall" + "time" + + "github.com/Terry-Mao/goim/pkg/ip" + "github.com/opentracing/opentracing-go" + "github.com/rs/zerolog/log" + xlog "gitlab.33.cn/chat/dtalk/pkg/log" + "gitlab.33.cn/chat/dtalk/pkg/naming" + "gitlab.33.cn/chat/dtalk/service/record/pusher/config" + "gitlab.33.cn/chat/dtalk/service/record/pusher/server/grpc" + "gitlab.33.cn/chat/dtalk/service/record/pusher/service" + "gitlab.33.cn/chat/im-pkg/trace" +) + +const srvName = "pusher" + +var ( + // projectVersion 项目版本 + projectVersion = "0.6.6" + // goVersion go版本 + goVersion = "" + // gitCommit git提交commit id + gitCommit = "" + // buildTime 编译时间 + buildTime = "" + + isShowVersion = flag.Bool("v", false, "show project version") +) + +// showVersion 显示项目版本信息 +func showVersion(isShow bool) { + if isShow { + fmt.Printf("Project: %s\n", srvName) + fmt.Printf(" Version: %s\n", projectVersion) + fmt.Printf(" Go Version: %s\n", goVersion) + fmt.Printf(" Git Commit: %s\n", gitCommit) + fmt.Printf(" Build Time: %s\n", buildTime) + os.Exit(0) + } +} + +// @Title 聊天单模块集成测试 +// @Version 0.1 +// @Description +// @SecurityDefinitions.ApiKey ApiKeyAuth +// @In header +// @Name Authorization +// @BasePath / +func main() { + flag.Parse() + showVersion(*isShowVersion) + + if err := config.Init(); err != nil { + panic(err) + } + // log init + logger, err := xlog.Init(config.Conf.Log) + if err != nil { + panic(err) + } + // set global log instance + log.Logger = logger.With().Str("service", srvName).Logger() + log.Info(). + Str("AppId", config.Conf.AppId). + Str("Env", config.Conf.Env). + Interface("Reg", config.Conf.Reg). + Interface("IdGeneratorClient", config.Conf.IdGenRPCClient). + Interface("GroupClient", config.Conf.GroupRPCClient). + Interface("AnswerClient", config.Conf.AnswerRPCClient). + Interface("LogicClient", config.Conf.LogicRPCClient). + Interface("GRPCServer", config.Conf.GRPCServer). + Interface("Redis", config.Conf.Redis). + Interface("IMSub", config.Conf.IMSub). + Interface("RevSub", config.Conf.RevSub). + Interface("StorePub", config.Conf.StorePub). + Interface("OffPushPub", config.Conf.OffPush). + Msg("config info") + + // trace init + tracer, tracerCloser := trace.Init(srvName, config.Conf.Trace) + //不然后续不会有Jaeger实例 + opentracing.SetGlobalTracer(tracer) + + // service init + svc := service.New(config.Conf) + rpc := grpc.New(config.Conf.GRPCServer, svc, log.Logger) + svc.ListenMQ() + + // register server + _, port, _ := net.SplitHostPort(config.Conf.GRPCServer.Addr) + addr := fmt.Sprintf("%s:%s", ip.InternalIP(), port) + if err := naming.Register(config.Conf.Reg.RegAddrs, config.Conf.Reg.SrvName, addr, config.Conf.Reg.Schema, 15); err != nil { + panic(err) + } + fmt.Println("register ok") + + // init signal + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT) + for { + s := <-c + log.Info().Str("signal", s.String()).Msg("service get a signal") + switch s { + case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT: + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := naming.UnRegister(config.Conf.Reg.SrvName, addr, config.Conf.Reg.Schema); err != nil { + log.Error().Err(err).Msg("naming.UnRegister") + } + if err := rpc.Shutdown(ctx); err != nil { + log.Error().Err(err).Msg("rpc.Shutdown") + } + if err := tracerCloser.Close(); err != nil { + log.Error().Err(err).Msg("tracer close failed") + } + svc.Shutdown(ctx) + time.Sleep(time.Second * 2) + log.Info().Str("name", srvName).Msg("server exit") + return + case syscall.SIGHUP: + // TODO reload + default: + return + } + } +} diff --git a/service/record/pusher/config/config.go b/service/record/pusher/config/config.go new file mode 100644 index 0000000..75f31e6 --- /dev/null +++ b/service/record/pusher/config/config.go @@ -0,0 +1,206 @@ +package config + +import ( + "flag" + "github.com/uber/jaeger-client-go" + traceConfig "github.com/uber/jaeger-client-go/config" + "os" + "time" + + "github.com/BurntSushi/toml" + xlog "gitlab.33.cn/chat/dtalk/pkg/log" + "gitlab.33.cn/chat/dtalk/pkg/net/grpc" + xgrpc "gitlab.33.cn/chat/dtalk/pkg/net/grpc" + xtime "gitlab.33.cn/chat/dtalk/pkg/time" +) + +var ( + confPath string + regAddrs string + env string + + Conf *Config +) + +func init() { + var ( + defAddrs = os.Getenv("REGADDRS") + defEnv = os.Getenv("DTALKENV") + ) + flag.StringVar(&confPath, "conf", "pusher.toml", "default config path.") + flag.StringVar(®Addrs, "reg", defAddrs, "etcd register addrs. eg:127.0.0.1:2379") + flag.StringVar(&env, "env", defEnv, "service runtime environment") +} + +// Init init config. +func Init() (err error) { + Conf = Default() + _, err = toml.DecodeFile(confPath, &Conf) + return +} + +func Default() *Config { + return &Config{ + AppId: "dtalk", + Engine: "standard", + Env: env, + Log: xlog.Config{ + Level: xlog.DebugLevel, + Mode: xlog.ConsoleMode, + Path: "", + Display: xlog.JsonDisplay, + }, + Trace: traceConfig.Configuration{ + ServiceName: "pusher", + Gen128Bit: true, + Sampler: &traceConfig.SamplerConfig{ + Type: jaeger.SamplerTypeConst, + Param: 1, + }, + Reporter: &traceConfig.ReporterConfig{ + LogSpans: true, + LocalAgentHostPort: "127.0.0.1:6831", + }, + }, + GRPCServer: &xgrpc.ServerConfig{ + Network: "tcp", + Addr: ":30002", + Timeout: xtime.Duration(time.Second), + KeepAliveMaxConnectionIdle: xtime.Duration(time.Second * 60), + KeepAliveMaxConnectionAge: xtime.Duration(time.Hour * 2), + KeepAliveMaxMaxConnectionAgeGrace: xtime.Duration(time.Second * 20), + KeepAliveTime: xtime.Duration(time.Second * 60), + KeepAliveTimeout: xtime.Duration(time.Second * 20), + }, + Reg: &Reg{ + Schema: "dtalk", + SrvName: "pusher", + RegAddrs: regAddrs, + }, + Redis: &Redis{ + Network: "tcp", + Addr: "127.0.0.1:6379", + Auth: "", + Active: 60000, + Idle: 1024, + DialTimeout: xtime.Duration(200 * time.Millisecond), + ReadTimeout: xtime.Duration(500 * time.Millisecond), + WriteTimeout: xtime.Duration(500 * time.Millisecond), + IdleTimeout: xtime.Duration(120 * time.Second), + Expire: xtime.Duration(30 * time.Minute), + }, + LogicRPCClient: &RPCClient{ + RegAddrs: "127.0.0.1:2379", + Schema: "im", + SrvName: "logic", + Dial: xtime.Duration(time.Second), + Timeout: xtime.Duration(time.Second), + }, + IdGenRPCClient: &RPCClient{ + RegAddrs: "127.0.0.1:2379", + Schema: "dtalk", + SrvName: "generator", + Dial: xtime.Duration(time.Second), + Timeout: xtime.Duration(time.Second), + }, + GroupRPCClient: &RPCClient{ + RegAddrs: "127.0.0.1:2379", + Schema: "dtalk", + SrvName: "group", + Dial: xtime.Duration(time.Second), + Timeout: xtime.Duration(time.Second), + }, + AnswerRPCClient: &RPCClient{ + RegAddrs: "127.0.0.1:2379", + Schema: "dtalk", + SrvName: "answer", + Dial: xtime.Duration(time.Second), + Timeout: xtime.Duration(time.Second), + }, + IMSub: &MQSubClient{ + Brokers: []string{"127.0.0.1:9092"}, + Number: 16, + MaxWorker: 1024, + }, + RevSub: &MQSubClient{ + Brokers: []string{"127.0.0.1:9092"}, + Number: 16, + MaxWorker: 1024, + }, + StorePub: &MQPubServer{ + Brokers: []string{"127.0.0.1:9092"}, + }, + OffPush: &OffPush{ + IsEnabled: true, + OffPushPub: &MQPubServer{ + Brokers: []string{"127.0.0.1:9092"}, + }, + }, + } +} + +type Config struct { + AppId string + Engine string + Env string + Log xlog.Config + Trace traceConfig.Configuration + LogicRPCClient *RPCClient + IdGenRPCClient *RPCClient + GroupRPCClient *RPCClient + AnswerRPCClient *RPCClient + //gRPC server + GRPCServer *grpc.ServerConfig + Reg *Reg + Redis *Redis + IMSub *MQSubClient + RevSub *MQSubClient + StorePub *MQPubServer + OffPush *OffPush +} + +// Redis . +type Redis struct { + Network string + Addr string + Auth string + Active int + Idle int + DialTimeout xtime.Duration + ReadTimeout xtime.Duration + WriteTimeout xtime.Duration + IdleTimeout xtime.Duration + Expire xtime.Duration +} + +// Reg is service register/discovery config +type Reg struct { + Schema string + SrvName string // call + RegAddrs string // etcd addrs, seperate by ',' +} + +// RPCClient is RPC client config. +type RPCClient struct { + RegAddrs string // etcd addrs, seperate by ',' + Schema string + SrvName string // call + Dial xtime.Duration + Timeout xtime.Duration +} + +type MQSubClient struct { + Brokers []string + Number uint32 + MaxWorker int +} + +type OffPush struct { + IsEnabled bool + OffPushPub *MQPubServer +} + +// Kafka . +type MQPubServer struct { + Brokers []string +} diff --git a/service/record/pusher/config/pusher.toml b/service/record/pusher/config/pusher.toml new file mode 100644 index 0000000..ffe6cda --- /dev/null +++ b/service/record/pusher/config/pusher.toml @@ -0,0 +1,93 @@ +AppId= "dtalk" +Engine= "standard" +Env= "debug" + +[log] +Level="debug" +Mode="console" +Path="" +Display="json" + +[Trace] + ServiceName = "pusher" +Gen128Bit = true +[Trace.Sampler] + Type="const" + Param=1.0 +[Trace.Reporter] +LogSpans = true +LocalAgentHostPort = "127.0.0.1:6831" + +[GRPCServer] +Network= "tcp" +Addr= ":30003" +Timeout= "1s" +KeepAliveMaxConnectionIdle= "60s" +KeepAliveMaxConnectionAge= "2h" +KeepAliveMaxMaxConnectionAgeGrace= "20s" +KeepAliveTime= "60s" +KeepAliveTimeout= "20s" + +[reg] +schema = "dtalk" +srvName = "pusher" +regAddrs = "127.0.0.1:2379" + +[LogicRPCClient] +regAddrs = "127.0.0.1:2379" +Schema = "im" +SrvName = "logic" +Dial = "1s" +Timeout = "1s" + +[idGenRPCClient] +regAddrs = "127.0.0.1:2379" +schema = "dtalk" +srvName = "generator" +dial = "1s" +timeout = "1s" + +[GroupRPCClient] +regAddrs = "127.0.0.1:2379" +schema = "dtalk" +srvName = "group" +dial = "1s" +timeout = "1s" + +[AnswerRPCClient] +regAddrs = "127.0.0.1:2379" +schema = "dtalk" +srvName = "answer" +dial = "1s" +timeout = "1s" + +[Redis] +network="tcp" +addr="127.0.0.1:6379" +auth="" +active=60000 +idle=1024 +dialTimeout="200ms" +readTimeout="500ms" +writeTimeout="500ms" +idleTimeout="120s" +expire="30m" + +[IMSub] +Brokers=["127.0.0.1:9092"] +Number=16 +MaxWorker=1024 + +[RevSub] +Brokers=["127.0.0.1:9092"] +Number=16 +MaxWorker=1024 + +[StorePub] +Brokers = ["127.0.0.1:9092"] + +[OffPush] +IsEnabled = true + +[OffPush.OffPushPub] +Brokers = ["127.0.0.1:9092"] diff --git a/service/record/pusher/dao/dao.go b/service/record/pusher/dao/dao.go new file mode 100644 index 0000000..e9d9a19 --- /dev/null +++ b/service/record/pusher/dao/dao.go @@ -0,0 +1,89 @@ +package dao + +import ( + "fmt" + "time" + + "github.com/gomodule/redigo/redis" + "github.com/rs/zerolog" + zlog "github.com/rs/zerolog/log" + "gitlab.33.cn/chat/dtalk/pkg/naming" + "gitlab.33.cn/chat/dtalk/pkg/net/grpc" + idgen "gitlab.33.cn/chat/dtalk/service/generator/api" + groupApi "gitlab.33.cn/chat/dtalk/service/group/api" + "gitlab.33.cn/chat/dtalk/service/record/kafka/publisher" + "gitlab.33.cn/chat/dtalk/service/record/pusher/config" + "google.golang.org/grpc/resolver" + kafka "gopkg.in/Shopify/sarama.v1" +) + +type Dao struct { + appId string + log zerolog.Logger + redis *redis.Pool + idGenRPCClient idgen.GeneratorClient + groupRPCClient groupApi.GroupClient + storePub kafka.SyncProducer + offPushPub kafka.SyncProducer +} + +func New(c *config.Config) *Dao { + d := &Dao{ + appId: c.AppId, + log: zlog.Logger, + redis: newRedis(c.Redis), + idGenRPCClient: newIdGenClient(c), + groupRPCClient: newGroupClient(c), + storePub: publisher.NewKafkaPub(c.StorePub.Brokers), + offPushPub: publisher.NewKafkaPub(c.OffPush.OffPushPub.Brokers), + } + return d +} + +func newRedis(c *config.Redis) *redis.Pool { + return &redis.Pool{ + MaxIdle: c.Idle, + MaxActive: c.Active, + IdleTimeout: time.Duration(c.IdleTimeout), + Dial: func() (redis.Conn, error) { + conn, err := redis.Dial(c.Network, c.Addr, + redis.DialConnectTimeout(time.Duration(c.DialTimeout)), + redis.DialReadTimeout(time.Duration(c.ReadTimeout)), + redis.DialWriteTimeout(time.Duration(c.WriteTimeout)), + redis.DialPassword(c.Auth), + ) + if err != nil { + return nil, err + } + return conn, nil + }, + } +} + +func newIdGenClient(cfg *config.Config) idgen.GeneratorClient { + rb := naming.NewResolver(cfg.IdGenRPCClient.RegAddrs, cfg.IdGenRPCClient.Schema) + resolver.Register(rb) + + addr := fmt.Sprintf("%s:///%s", cfg.IdGenRPCClient.Schema, cfg.IdGenRPCClient.SrvName) // "schema://[authority]/service" + fmt.Println("rpc client call addr:", addr) + + conn, err := grpc.NewGRPCConn(addr, time.Duration(cfg.IdGenRPCClient.Dial)) + if err != nil { + panic(err) + } + return idgen.NewGeneratorClient(conn) +} + +func newGroupClient(cfg *config.Config) groupApi.GroupClient { + rb := naming.NewResolver(cfg.GroupRPCClient.RegAddrs, cfg.GroupRPCClient.Schema) + resolver.Register(rb) + + addr := fmt.Sprintf("%s:///%s", cfg.GroupRPCClient.Schema, cfg.GroupRPCClient.SrvName) // "schema://[authority]/service" + fmt.Println("rpc client call addr:", addr) + + conn, err := grpc.NewGRPCConn(addr, time.Duration(cfg.GroupRPCClient.Dial)) + if err != nil { + panic(err) + } + return groupApi.NewGroupClient(conn) +} diff --git a/service/record/pusher/dao/dao_test.go b/service/record/pusher/dao/dao_test.go new file mode 100644 index 0000000..88b7be6 --- /dev/null +++ b/service/record/pusher/dao/dao_test.go @@ -0,0 +1,37 @@ +package dao + +import ( + "os" + "testing" + "time" + + "github.com/gomodule/redigo/redis" + "github.com/rs/zerolog" + zlog "github.com/rs/zerolog/log" + "gitlab.33.cn/chat/dtalk/pkg/mysql" + xtime "gitlab.33.cn/chat/dtalk/pkg/time" + "gitlab.33.cn/chat/dtalk/service/record/pusher/config" +) + +var ( + testLog zerolog.Logger + testConn *mysql.MysqlConn + testRedis *redis.Pool +) + +func TestMain(m *testing.M) { + testLog = zlog.Logger + testRedis = newRedis(&config.Redis{ + Network: "tcp", + Addr: "127.0.0.1:6379", + Auth: "", + Active: 60000, + Idle: 1024, + DialTimeout: xtime.Duration(200 * time.Millisecond), + ReadTimeout: xtime.Duration(500 * time.Millisecond), + WriteTimeout: xtime.Duration(500 * time.Millisecond), + IdleTimeout: xtime.Duration(120 * time.Second), + Expire: xtime.Duration(30 * time.Minute), + }) + os.Exit(m.Run()) +} diff --git a/service/record/pusher/dao/group.go b/service/record/pusher/dao/group.go new file mode 100644 index 0000000..ead6a9f --- /dev/null +++ b/service/record/pusher/dao/group.go @@ -0,0 +1,29 @@ +package dao + +import ( + "context" + "time" + + groupApi "gitlab.33.cn/chat/dtalk/service/group/api" +) + +func (d *Dao) GetAllJoinedGroups(ctx context.Context, uid string) (groups []int64, err error) { + var ( + req groupApi.GetGroupIdsRequest + reply *groupApi.GetGroupIdsReply + ) + req.MemberId = uid + ctx, cancel := context.WithDeadline(ctx, time.Now().Add(time.Second*3)) + defer cancel() + reply, err = d.groupRPCClient.GetGroupIds(ctx, &req) + if err != nil { + return + } + + return reply.GroupIds, nil +} + +func (d *Dao) GetGroupSession(cid string, seq int32) (session string, err error) { + //TODO call logic get log mark by connect seq + return "", nil +} diff --git a/service/record/pusher/dao/idg.go b/service/record/pusher/dao/idg.go new file mode 100644 index 0000000..896e485 --- /dev/null +++ b/service/record/pusher/dao/idg.go @@ -0,0 +1,23 @@ +package dao + +import ( + "context" + idgen "gitlab.33.cn/chat/dtalk/service/generator/api" + "time" +) + +// Receive receive a message. +func (d *Dao) GetLogId(ctx context.Context) (id int64, err error) { + var ( + req idgen.Empty + reply *idgen.GetIDReply + ) + ctx, cancel := context.WithDeadline(ctx, time.Now().Add(time.Second*3)) + defer cancel() + reply, err = d.idGenRPCClient.GetID(ctx, &req) + if err != nil { + return + } + + return reply.Id, nil +} diff --git a/service/record/pusher/dao/kafka.go b/service/record/pusher/dao/kafka.go new file mode 100644 index 0000000..c3eaeef --- /dev/null +++ b/service/record/pusher/dao/kafka.go @@ -0,0 +1,112 @@ +package dao + +import ( + "context" + "fmt" + "gitlab.33.cn/chat/imparse" + + "github.com/golang/protobuf/proto" + record "gitlab.33.cn/chat/dtalk/service/record/proto" + "gopkg.in/Shopify/sarama.v1" +) + +// PushMsg push a message to databus. +func (d *Dao) BatchPushPublish(ctx context.Context, key, fromId string) (err error) { + pushMsg := &record.RecordDeal{ + AppId: d.appId, + FromId: fromId, + Key: key, + Opt: record.Operation_BatchPush, + Msg: nil, + } + b, err := proto.Marshal(pushMsg) + if err != nil { + return + } + appTopic := fmt.Sprintf("store-%s-topic", d.appId) + m := &sarama.ProducerMessage{ + Key: sarama.StringEncoder(fromId), + Topic: appTopic, + Value: sarama.ByteEncoder(b), + } + if _, _, err = d.storePub.SendMessage(m); err != nil { + d.log.Error().Err(err).Msg("kafkaPub.SendMessage error") + } + return +} + +// PushMsg push a message to databus. +func (d *Dao) MarkReadPublish(ctx context.Context, key, fromId string, tp imparse.FrameType, mids []int64) (err error) { + msg, err := proto.Marshal(&record.Marked{ + Type: string(tp), + Mids: mids, + }) + if err != nil { + return + } + pushMsg := &record.RecordDeal{ + AppId: d.appId, + FromId: fromId, + Key: key, + Opt: record.Operation_MarkRead, + Msg: msg, + } + b, err := proto.Marshal(pushMsg) + if err != nil { + return + } + appTopic := fmt.Sprintf("store-%s-topic", d.appId) + m := &sarama.ProducerMessage{ + Key: sarama.StringEncoder(fromId), + Topic: appTopic, + Value: sarama.ByteEncoder(b), + } + if _, _, err = d.storePub.SendMessage(m); err != nil { + d.log.Error().Err(err).Msg("kafkaPub.SendMessage error") + } + return +} + +// PushMsg push a message to databus. +func (d *Dao) SyncPublish(ctx context.Context, key, fromId string, mid int64) (err error) { + msg, err := proto.Marshal(&record.Sync{ + Mid: mid, + }) + if err != nil { + return + } + pushMsg := &record.RecordDeal{ + AppId: d.appId, + FromId: fromId, + Key: key, + Opt: record.Operation_SyncMsg, + Msg: msg, + } + b, err := proto.Marshal(pushMsg) + if err != nil { + return + } + appTopic := fmt.Sprintf("store-%s-topic", d.appId) + m := &sarama.ProducerMessage{ + Key: sarama.StringEncoder(fromId), + Topic: appTopic, + Value: sarama.ByteEncoder(b), + } + if _, _, err = d.storePub.SendMessage(m); err != nil { + d.log.Error().Err(err).Msg("kafkaPub.SendMessage error") + } + return +} + +func (d *Dao) PublishOfflineMsg(ctx context.Context, fromId string, b []byte) (err error) { + appTopic := fmt.Sprintf("offpush-%s-topic", d.appId) + m := &sarama.ProducerMessage{ + Key: sarama.StringEncoder(fromId), + Topic: appTopic, + Value: sarama.ByteEncoder(b), + } + if _, _, err = d.offPushPub.SendMessage(m); err != nil { + d.log.Error().Err(err).Bytes("pushMsg", b).Msg("kafkaPub.SendMessage error") + } + return +} diff --git a/service/record/pusher/dao/redis.go b/service/record/pusher/dao/redis.go new file mode 100644 index 0000000..6bfefb3 --- /dev/null +++ b/service/record/pusher/dao/redis.go @@ -0,0 +1,294 @@ +package dao + +import ( + "encoding/json" + "fmt" + "regexp" + "strings" + + "github.com/gomodule/redigo/redis" + "github.com/pkg/errors" + "gitlab.33.cn/chat/dtalk/service/record/pusher/logH" + "gitlab.33.cn/chat/dtalk/service/record/pusher/model" +) + +const ( + _prefixConnSeq = "conn-seq:%v" + _prefixDevice = "device:%v" + _prefixDeviceToken = "device_token:%v" +) + +func keyConnection(cid string) string { + return fmt.Sprintf(_prefixConnSeq, cid) +} + +func keyDevice(uid string) string { + return fmt.Sprintf(_prefixDevice, uid) +} + +func keyDeviceToken(deviceToken string) string { + return fmt.Sprintf(_prefixDeviceToken, deviceToken) +} + +func connSeqIndexValConvert(tp string, logs []string) (string, error) { + val := fmt.Sprintf("type=%s;", tp) + val += strings.Join(logs, ",") + //check + reg := regexp.MustCompile(`^type=(\S+);`) + if !reg.MatchString(val) { + return val, errors.New("conn seq index convert err") + } + return val, nil +} + +func connSeqIndexValParse(val string) ([]string, error) { + reg := regexp.MustCompile(`^type=(\S+);`) + if !reg.MatchString(val) { + return nil, errors.New("conn seq index parse err") + } + //找出type子串 + typeCnt := reg.FindStringSubmatch(val) + if len(typeCnt) < 2 { + return nil, errors.New("conn seq index parse err: cnt len < 2") + } + //找出;所在的尾部下标 + typeIdx := reg.FindStringIndex(val) + if len(typeIdx) < 2 { + return nil, errors.New("conn seq index parse err: idx len < 2") + } + log := strings.Split(val[typeIdx[1]:], ",") + var ret = make([]string, len(log)+1) + copy(ret[1:], log) + ret[0] = typeCnt[1] + return ret, nil +} + +//key:connect id; val:logs id +func (d *Dao) AddConnSeqIndex(cid string, seq int32, item *logH.ConnSeqItem) error { + key := keyConnection(cid) + conn := d.redis.Get() + defer conn.Close() + val, err := json.Marshal(item) + if err != nil { + return err + } + if err := conn.Send("HSET", key, seq, val); err != nil { + d.log.Error().Err(err).Msg(fmt.Sprintf("conn.Send(HSET %s,%d,%s)", key, seq, val)) + return err + } + if err := conn.Flush(); err != nil { + d.log.Error().Err(err).Msg("conn.Flush()") + return err + } + if _, err := conn.Receive(); err != nil { + d.log.Error().Err(err).Msg("conn.Receive()") + return err + } + return nil +} + +func (d *Dao) GetConnSeqIndex(cid string, seq int32) (*logH.ConnSeqItem, error) { + key := keyConnection(cid) + conn := d.redis.Get() + defer conn.Close() + data, err := redis.String(conn.Do("HGET", key, seq)) + if err != nil { + if err == redis.ErrNil { + return nil, nil + } + d.log.Error().Err(err).Msg(fmt.Sprintf("conn.DO(HGET %s, %d)", key, seq)) + return nil, err + } + var item logH.ConnSeqItem + err = json.Unmarshal([]byte(data), &item) + if err != nil { + return nil, err + } + //logsStr := ret[1:] + //tp := ret[0] + //var logs = make([]uint64, len(logsStr)) + //for i, l := range logsStr { + // log, err := strconv.ParseInt(l, 10, 64) + // if err != nil { + // return "", nil, err + // } + // logs[i] = uint64(log) + //} + //return bizroto.EventType(tp), logs, nil + return &item, nil +} + +func (d *Dao) ClearConnSeq(cid string) error { + key := keyConnection(cid) + conn := d.redis.Get() + defer conn.Close() + + if err := conn.Send("DEL", key); err != nil { + d.log.Error().Err(err).Msg(fmt.Sprintf("conn.Send(DEL %s)", key)) + return err + } + if err := conn.Flush(); err != nil { + d.log.Error().Err(err).Msg("conn.Flush()") + return err + } + if _, err := conn.Receive(); err != nil { + d.log.Error().Err(err).Msg("conn.Receive()") + return err + } + return nil +} + +//hash key:userId +//hash val: key=device val=json boj +func (d *Dao) AddDeviceInfo(device *model.DeviceInfo) error { + val, err := json.Marshal(device) + if err != nil { + return err + } + key := keyDevice(device.Uid) + conn := d.redis.Get() + defer conn.Close() + if err := conn.Send("HSET", key, device.ConnectId, val); err != nil { + d.log.Error().Err(err).Msg(fmt.Sprintf("conn.Send(HSET %s,%s,%s)", key, device.ConnectId, val)) + return err + } + //添加deviceToken-uid索引 + keyDT := keyDeviceToken(device.DeviceToken) + if err := conn.Send("SET", keyDT, device.Uid); err != nil { + d.log.Error().Err(err).Msg(fmt.Sprintf("conn.Send(SET %s,%s)", keyDT, device.Uid)) + return err + } + if err := conn.Flush(); err != nil { + d.log.Error().Err(err).Msg("conn.Flush()") + return err + } + n := 2 + for i := 0; i < n; i++ { + if _, err := conn.Receive(); err != nil { + d.log.Error().Err(err).Msg("conn.Receive()") + return err + } + } + return d.setExpire(device) +} + +//hash key:userId +//hash val: key=device val=json boj +func (d *Dao) setExpire(device *model.DeviceInfo) error { + key := keyDevice(device.Uid) + conn := d.redis.Get() + defer conn.Close() + nMap, err := redis.StringMap(conn.Do("HGETALL", key)) + if err != nil { + d.log.Error().Err(err).Msg(fmt.Sprintf("conn.DO(HGETALL %s)", key)) + return err + } + n := 0 + for _, val := range nMap { + item := model.DeviceInfo{} + err := json.Unmarshal([]byte(val), &item) + if err != nil { + return err + } + if item.ConnectId != device.ConnectId && item.DeviceType == device.DeviceType { + n++ + if err := conn.Send("HDEL", keyDevice(item.Uid), item.ConnectId); err != nil { + d.log.Error().Err(err).Msg(fmt.Sprintf("conn.Send(HDEL %s,%s)", keyDevice(item.Uid), item.ConnectId)) + return err + } + } + } + if err := conn.Flush(); err != nil { + d.log.Error().Err(err).Msg("conn.Flush()") + return err + } + for i := 0; i < n; i++ { + if _, err := conn.Receive(); err != nil { + d.log.Error().Err(err).Msg("conn.Receive()") + return err + } + } + return nil +} + +//hash key:userId +//hash val: key=device val=json boj +func (d *Dao) EnableDeviceInfo(uid, connId string) error { + //读出deviceInfo + key := keyDevice(uid) + conn := d.redis.Get() + defer conn.Close() + data, err := redis.Bytes(conn.Do("HGET", key, connId)) + if err != nil { + d.log.Error().Err(err).Msg(fmt.Sprintf("conn.DO(HGET %s,%s)", key, connId)) + return err + } + var device model.DeviceInfo + err = json.Unmarshal(data, &device) + if err != nil { + return err + } + device.IsEnabled = true + + //写回deviceInfo + val, err := json.Marshal(device) + if err != nil { + return err + } + if err := conn.Send("HSET", key, device.ConnectId, val); err != nil { + d.log.Error().Err(err).Msg(fmt.Sprintf("conn.Send(HSET %s,%s,%s)", key, device.ConnectId, val)) + return err + } + if err := conn.Flush(); err != nil { + d.log.Error().Err(err).Msg("conn.Flush()") + return err + } + n := 1 + for i := 0; i < n; i++ { + if _, err := conn.Receive(); err != nil { + d.log.Error().Err(err).Msg("conn.Receive()") + return err + } + } + return nil +} + +//key:userId; val:json +func (d *Dao) GetAllDevices(uid string) ([]*model.DeviceInfo, error) { + key := keyDevice(uid) + conn := d.redis.Get() + defer conn.Close() + nMap, err := redis.StringMap(conn.Do("HGETALL", key)) + if err != nil { + d.log.Error().Err(err).Msg(fmt.Sprintf("conn.DO(HGETALL %s)", key)) + return nil, err + } + devices := make([]*model.DeviceInfo, 0) + for _, val := range nMap { + item := model.DeviceInfo{} + err := json.Unmarshal([]byte(val), &item) + if err != nil { + return nil, err + } + //获取deviceToken所属的uid + item.DTUid, err = d.GetTokenDevice(item.DeviceToken) + if err != nil { + continue + } + devices = append(devices, &item) + } + return devices, nil +} + +//key:userId; val:json +func (d *Dao) GetTokenDevice(deviceToken string) (string, error) { + key := keyDeviceToken(deviceToken) + conn := d.redis.Get() + defer conn.Close() + uid, err := redis.String(conn.Do("GET", key)) + if err != nil { + d.log.Error().Err(err).Msg(fmt.Sprintf("conn.DO(GET %s)", key)) + return "", err + } + return uid, nil +} diff --git a/service/record/pusher/dao/redis_test.go b/service/record/pusher/dao/redis_test.go new file mode 100644 index 0000000..7b57abf --- /dev/null +++ b/service/record/pusher/dao/redis_test.go @@ -0,0 +1,273 @@ +package dao + +import ( + "gitlab.33.cn/chat/dtalk/pkg/util" + "gitlab.33.cn/chat/dtalk/service/generator/api" + group "gitlab.33.cn/chat/dtalk/service/group/api" + answer "gitlab.33.cn/chat/dtalk/service/record/answer/api" + "testing" + "time" + + "github.com/gomodule/redigo/redis" + "github.com/rs/zerolog" + "gitlab.33.cn/chat/dtalk/service/record/pusher/model" + "gopkg.in/Shopify/sarama.v1" +) + +func TestDao_GetAllDevices(t *testing.T) { + type fields struct { + log zerolog.Logger + redis *redis.Pool + offPushPub sarama.SyncProducer + } + type args struct { + uid string + } + tests := []struct { + name string + fields fields + args args + want []*model.DeviceInfo + wantErr bool + }{ + { + name: "", + fields: fields{ + log: testLog, + redis: testRedis, + offPushPub: nil, + }, + args: args{ + uid: "test-uid", + }, + want: nil, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + d := &Dao{ + log: tt.fields.log, + redis: tt.fields.redis, + offPushPub: tt.fields.offPushPub, + } + got, err := d.GetAllDevices(tt.args.uid) + if (err != nil) != tt.wantErr { + t.Errorf("GetAllDevices() error = %v, wantErr %v", err, tt.wantErr) + return + } + for _, info := range got { + t.Log("got", info) + } + }) + } +} + +func Test_connSeqIndexValConvert(t *testing.T) { + got, err := connSeqIndexValConvert("common", []string{"1", "2"}) + if err != nil { + t.Errorf("connSeqIndexValConvert() error = %v", err) + return + } + t.Logf("got=%s", got) + + got2, err2 := connSeqIndexValConvert("common", []string{}) + t.Logf("got2=%s, err= %v", got2, err2) +} + +func Test_connSeqIndexValParse(t *testing.T) { + { + got, err := connSeqIndexValParse("type=common;1,2") + t.Logf("index=%d,got=%s,err=%v\n", 1, got, err) + } + { + got, err := connSeqIndexValParse("type=common;1") + t.Logf("index=%d,got=%s,err=%v\n", 2, got, err) + } + { + got, err := connSeqIndexValParse("type=common;") + t.Logf("index=%d,got=%s,err=%v\n", 3, got, err) + } + { + got, err := connSeqIndexValParse("type=common1,2") + t.Logf("index=%d,got=%s,err=%v\n", 4, got, err) + } + { + got, err := connSeqIndexValParse("type=;1,2") + t.Logf("index=%d,got=%s,err=%v\n", 4, got, err) + } +} + +func TestDao_AddDeviceInfo(t *testing.T) { + type fields struct { + appId string + log zerolog.Logger + redis *redis.Pool + idGenRPCClient api.GeneratorClient + groupRPCClient group.GroupClient + answerRPCClient answer.AnswerClient + storePub sarama.SyncProducer + offPushPub sarama.SyncProducer + } + type args struct { + device *model.DeviceInfo + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "", + fields: fields{ + log: testLog, + redis: testRedis, + offPushPub: nil, + }, + args: args{ + device: &model.DeviceInfo{ + Uid: "test-uid", + ConnectId: "test-conn", + DeviceType: int32(0), + Username: "test-name", + DeviceToken: "test-deviceToken", + IsEnabled: false, + AddTime: uint64(util.TimeNowUnixNano() / int64(time.Millisecond)), + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + d := &Dao{ + appId: tt.fields.appId, + log: tt.fields.log, + redis: tt.fields.redis, + idGenRPCClient: tt.fields.idGenRPCClient, + groupRPCClient: tt.fields.groupRPCClient, + answerRPCClient: tt.fields.answerRPCClient, + storePub: tt.fields.storePub, + offPushPub: tt.fields.offPushPub, + } + if err := d.AddDeviceInfo(tt.args.device); (err != nil) != tt.wantErr { + t.Errorf("AddDeviceInfo() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestDao_EnableDeviceInfo(t *testing.T) { + type fields struct { + appId string + log zerolog.Logger + redis *redis.Pool + idGenRPCClient api.GeneratorClient + groupRPCClient group.GroupClient + answerRPCClient answer.AnswerClient + storePub sarama.SyncProducer + offPushPub sarama.SyncProducer + } + type args struct { + uid string + connId string + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "", + fields: fields{ + log: testLog, + redis: testRedis, + offPushPub: nil, + }, + args: args{ + uid: "test-uid", + connId: "test-conn", + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + d := &Dao{ + appId: tt.fields.appId, + log: tt.fields.log, + redis: tt.fields.redis, + idGenRPCClient: tt.fields.idGenRPCClient, + groupRPCClient: tt.fields.groupRPCClient, + answerRPCClient: tt.fields.answerRPCClient, + storePub: tt.fields.storePub, + offPushPub: tt.fields.offPushPub, + } + if err := d.EnableDeviceInfo(tt.args.uid, tt.args.connId); (err != nil) != tt.wantErr { + t.Errorf("EnableDeviceInfo() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestDao_setExpire(t *testing.T) { + type fields struct { + appId string + log zerolog.Logger + redis *redis.Pool + idGenRPCClient api.GeneratorClient + groupRPCClient group.GroupClient + answerRPCClient answer.AnswerClient + storePub sarama.SyncProducer + offPushPub sarama.SyncProducer + } + type args struct { + device *model.DeviceInfo + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "", + fields: fields{ + log: testLog, + redis: testRedis, + offPushPub: nil, + }, + args: args{ + device: &model.DeviceInfo{ + Uid: "115Mkz6vxW61Phj3UM3MPft1mCLrEiXUrQ", + ConnectId: "f471d531-2f77-452c-b80b-47b232aeb772", + DeviceType: 0, + Username: "dld", + DeviceToken: "", + IsEnabled: false, + AddTime: 1631764139040, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + d := &Dao{ + appId: tt.fields.appId, + log: tt.fields.log, + redis: tt.fields.redis, + idGenRPCClient: tt.fields.idGenRPCClient, + groupRPCClient: tt.fields.groupRPCClient, + answerRPCClient: tt.fields.answerRPCClient, + storePub: tt.fields.storePub, + offPushPub: tt.fields.offPushPub, + } + if err := d.setExpire(tt.args.device); (err != nil) != tt.wantErr { + t.Errorf("setExpire() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/service/record/pusher/logH/log_helper.go b/service/record/pusher/logH/log_helper.go new file mode 100644 index 0000000..4e6450b --- /dev/null +++ b/service/record/pusher/logH/log_helper.go @@ -0,0 +1,44 @@ +package logH + +//用于建立通信层协议seq和业务协议logId的映射关系 +type LogHelper struct { + engine Engine +} + +func NewLogHelper(e Engine) *LogHelper { + return &LogHelper{engine: e} +} + +func (lh *LogHelper) sessionToSeq(session string) int32 { + //TODO + return 0 +} + +func (lh *LogHelper) GetLogsIndex(key string, seq int32) (*ConnSeqItem, error) { + item, err := lh.engine.GetConnSeqIndex(key, seq) + if err != nil { + return nil, err + } + if item == nil { + //查询group seq + session, err := lh.engine.GetGroupSession(key, seq) + if err != nil { + return nil, err + } + item, err = lh.engine.GetConnSeqIndex(key, lh.sessionToSeq(session)) + if err != nil { + return nil, err + } + } + return item, nil +} + +func (lh *LogHelper) Save(key string, seq int32, item *ConnSeqItem) error { + return lh.engine.AddConnSeqIndex(key, seq, item) +} + +func (lh *LogHelper) SaveProxy(key string, seq int32, item *ConnSeqItem) error { + //return lh.engine.AddConnSeqIndex(key, seq, item) + //TODO 实现代理记录索引 + return nil +} diff --git a/service/record/pusher/logH/type.go b/service/record/pusher/logH/type.go new file mode 100644 index 0000000..ab18a9f --- /dev/null +++ b/service/record/pusher/logH/type.go @@ -0,0 +1,14 @@ +package logH + +type Engine interface { + GetConnSeqIndex(cid string, seq int32) (*ConnSeqItem, error) + AddConnSeqIndex(cid string, seq int32, item *ConnSeqItem) error + GetGroupSession(cid string, seq int32) (session string, err error) +} + +type ConnSeqItem struct { + Type string `json:"type"` + Sender string `json:"sender"` + Client string `json:"client"` + Logs []int64 `json:"logs"` +} diff --git a/service/record/pusher/model/error.go b/service/record/pusher/model/error.go new file mode 100644 index 0000000..733e4d8 --- /dev/null +++ b/service/record/pusher/model/error.go @@ -0,0 +1,9 @@ +package model + +import "errors" + +var ( + ErrAppId = errors.New("appId not compared") + ErrConsumeRedo = errors.New("process msg failed") + ErrCustomNotSupport = errors.New("biz not support") +) diff --git a/service/record/pusher/model/model.go b/service/record/pusher/model/model.go new file mode 100644 index 0000000..f7cff36 --- /dev/null +++ b/service/record/pusher/model/model.go @@ -0,0 +1,12 @@ +package model + +type DeviceInfo struct { + Uid string `json:"uid"` + ConnectId string `json:"connectId"` + DeviceType int32 `json:"deviceType"` + Username string `json:"username"` + DeviceToken string `json:"deviceToken"` + IsEnabled bool `json:"isEnabled"` + AddTime uint64 `json:"addTime"` + DTUid string `json:"-"` +} diff --git a/service/record/pusher/server/grpc/server.go b/service/record/pusher/server/grpc/server.go new file mode 100644 index 0000000..8ca6c49 --- /dev/null +++ b/service/record/pusher/server/grpc/server.go @@ -0,0 +1,60 @@ +package grpc + +import ( + "context" + "time" + + "github.com/opentracing/opentracing-go" + "github.com/rs/zerolog" + "github.com/uber/jaeger-client-go" + xgrpc "gitlab.33.cn/chat/dtalk/pkg/net/grpc" + pb "gitlab.33.cn/chat/dtalk/service/record/pusher/api" + "gitlab.33.cn/chat/dtalk/service/record/pusher/service" + "gitlab.33.cn/chat/im-pkg/trace" + "google.golang.org/grpc" +) + +func New(c *xgrpc.ServerConfig, svr *service.Service, logger zerolog.Logger) *xgrpc.Server { + connectionTimeout := grpc.ConnectionTimeout(time.Duration(c.Timeout)) + ws := xgrpc.NewServer( + c, + connectionTimeout, + grpc.ChainUnaryInterceptor( + trace.OpentracingServerInterceptor, + OpentracingServerLogXInterceptor(logger, trace.TraceIDLogKey), + ), + ) + pb.RegisterPusherServer(ws.Server(), &server{pb.UnimplementedPusherServer{}, svr}) + ws, err := ws.Start() + if err != nil { + panic(err) + } + return ws +} + +func OpentracingServerLogXInterceptor(logger zerolog.Logger, fieldKey string) grpc.UnaryServerInterceptor { + return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { + span := opentracing.SpanFromContext(ctx) + var traceID string + if jgSpan, ok := span.Context().(jaeger.SpanContext); ok { + traceID = jgSpan.TraceID().String() + } + logger.UpdateContext(func(c zerolog.Context) zerolog.Context { + return c.Str(fieldKey, traceID) + }) + logger.UpdateContext(func(c zerolog.Context) zerolog.Context { + return c.Str("method", info.FullMethod) + }) + return handler(ctx, req) + } +} + +type server struct { + pb.UnimplementedPusherServer + svr *service.Service +} + +func (s *server) PushClient(ctx context.Context, req *pb.PushReq) (*pb.PushReply, error) { + err := s.svr.PushClient(ctx, req.Key, req.From, req.Mid, req.Target, req.Type, req.FrameType, req.Data) + return &pb.PushReply{}, err +} diff --git a/service/record/pusher/service/group.go b/service/record/pusher/service/group.go new file mode 100644 index 0000000..712a75c --- /dev/null +++ b/service/record/pusher/service/group.go @@ -0,0 +1,30 @@ +package service + +import ( + "context" + "strconv" + + logic "gitlab.33.cn/chat/im/api/logic/grpc" +) + +func (s *Service) JoinGroups(ctx context.Context, uid, key string) error { + groups, err := s.dao.GetAllJoinedGroups(ctx, uid) + if err != nil { + return err + } + + if len(groups) == 0 { + return nil + } + + var gids = make([]string, len(groups)) + for i, group := range groups { + gids[i] = strconv.FormatInt(group, 10) + } + _, err = s.logicClient.JoinGroupsByKeys(ctx, &logic.GroupsKey{ + AppId: s.cfg.AppId, + Keys: []string{key}, + Gid: gids, + }) + return err +} diff --git a/service/record/pusher/service/push.go b/service/record/pusher/service/push.go new file mode 100644 index 0000000..ecb10b7 --- /dev/null +++ b/service/record/pusher/service/push.go @@ -0,0 +1,220 @@ +package service + +import ( + "context" + "fmt" + "github.com/rs/zerolog" + "gitlab.33.cn/chat/dtalk/pkg/util" + "time" + + "github.com/golang/protobuf/proto" + offlinepush "gitlab.33.cn/chat/dtalk/service/offline-push/api" + record "gitlab.33.cn/chat/dtalk/service/record/proto" + "gitlab.33.cn/chat/dtalk/service/record/pusher/logH" + comet "gitlab.33.cn/chat/im/api/comet/grpc" + logic "gitlab.33.cn/chat/im/api/logic/grpc" + xproto "gitlab.33.cn/chat/imparse/proto" +) + +//just socket push +func (s *Service) PushUser(ctx context.Context, key, from, mid, target string, tp int32, frameType string, body []byte) error { + log := zerolog.Ctx(ctx).With().Str("Uid", from).Str("ConnId", key). + Str("Mid", mid).Str("Target", target).Int32("Push Type", tp).Str("Frame Type", frameType).Logger() + err := s.pusher.UniCast(ctx, &record.PushMsg{ + AppId: s.cfg.AppId, + FromId: from, + Mid: util.ToInt64(mid), + Key: key, + Target: target, + Msg: body, + Type: tp, + FrameType: frameType, + }) + if err != nil { + log.Error().Stack().Err(err).Msg("PushUser failed") + } + return nil +} + +//just socket push +func (s *Service) PushClient(ctx context.Context, key, from, mid, target string, tp int32, frameType string, body []byte) error { + log := zerolog.Ctx(ctx).With().Str("Uid", from).Str("ConnId", key). + Str("Mid", mid).Str("Target", target).Int32("Push Type", tp).Str("Frame Type", frameType).Logger() + err := s.pusher.UniCastDevices(ctx, &record.PushMsg{ + AppId: s.cfg.AppId, + FromId: from, + Mid: util.ToInt64(mid), + Key: key, + Target: target, + Msg: body, + Type: tp, + FrameType: frameType, + }) + if err != nil { + log.Error().Stack().Err(err).Msg("PushClient failed") + } + return nil +} + +// +type Pusher struct { + s *Service +} + +func (p *Pusher) UniCastDevices(ctx context.Context, m *record.PushMsg) error { + keysMsg := &logic.KeysMsg{ + AppId: m.GetAppId(), + ToKeys: []string{m.GetTarget()}, + Msg: m.GetMsg(), + } + + reply, err := p.s.logicClient.PushByKeys(ctx, keysMsg) + if err != nil { + return err + } + + index := comet.PushMsgReply{} + err = proto.Unmarshal(reply.Msg, &index) + if err != nil { + return fmt.Errorf("unmarshal PushMsgReply failed: %v", err) + } + item := &logH.ConnSeqItem{ + Type: m.GetFrameType(), + Sender: m.GetFromId(), + Client: m.GetKey(), + Logs: []int64{m.GetMid()}, + } + for cid, seq := range index.Index { + err := p.s.lh.Save(cid, seq, item) + if err != nil { + p.s.log.Warn().Err(err).Str("key", m.GetKey()).Str("from", m.GetFromId()).Str("cid", cid).Int32("seq", seq).Msg("AddConnSeqIndex failed") + } + } + return nil +} + +func (p *Pusher) UniCast(ctx context.Context, m *record.PushMsg) error { + midsMsg := &logic.MidsMsg{ + AppId: m.GetAppId(), + ToIds: []string{m.GetTarget()}, + Msg: m.GetMsg(), + } + + if p.s.cfg.OffPush.IsEnabled { + p.pushOffline(m, midsMsg.ToIds) + } + reply, err := p.s.logicClient.PushByMids(ctx, midsMsg) + if err != nil { + p.s.log.Debug().Err(err). + Str("appId", midsMsg.GetAppId()).Strs("toIds", midsMsg.GetToIds()).Int("len of msg", len(midsMsg.GetMsg())). + Msg("UniCast PushByMids Failed") + return err + } + p.s.log.Debug().Str("appId", midsMsg.GetAppId()).Strs("to ids", midsMsg.GetToIds()).Msg("UniCast success") + + index := comet.PushMsgReply{} + err = proto.Unmarshal(reply.Msg, &index) + if err != nil { + return fmt.Errorf("unmarshal PushMsgReply failed: %v", err) + } + item := &logH.ConnSeqItem{ + Type: m.GetFrameType(), + Sender: m.GetFromId(), + Client: m.GetKey(), + Logs: []int64{m.GetMid()}, + } + for cid, seq := range index.Index { + err := p.s.lh.Save(cid, seq, item) + if err != nil { + p.s.log.Warn().Err(err).Str("key", m.GetKey()).Str("from", m.GetFromId()).Str("cid", cid).Int32("seq", seq).Msg("AddConnSeqIndex failed") + } + } + return nil +} + +func (p *Pusher) GroupCast(ctx context.Context, m *record.PushMsg) error { + gMsg := &logic.GroupMsg{ + AppId: m.GetAppId(), + Group: m.GetTarget(), + Msg: m.GetMsg(), + } + + reply, err := p.s.logicClient.PushGroup(ctx, gMsg) + if err != nil { + p.s.log.Debug().Err(err). + Str("appId", gMsg.GetAppId()).Str("to group", gMsg.GetGroup()).Int("len of msg", len(gMsg.GetMsg())). + Msg("GroupCast PushGroup Failed") + return err + } + p.s.log.Debug().Str("appId", gMsg.AppId).Str("to group", gMsg.Group).Msg("GroupCast success") + + index := comet.PushMsgReply{} + err = proto.Unmarshal(reply.Msg, &index) + if err != nil { + return fmt.Errorf("unmarshal PushMsgReply failed: %v", err) + } + item := &logH.ConnSeqItem{ + Type: m.GetFrameType(), + Sender: m.GetFromId(), + Client: m.GetKey(), + Logs: []int64{m.GetMid()}, + } + for cid, seq := range index.Index { + err := p.s.lh.SaveProxy(cid, seq, item) + if err != nil { + p.s.log.Warn().Err(err).Str("key", m.GetKey()).Str("from", m.GetFromId()).Str("cid", cid).Int32("seq", seq).Msg("AddConnSeqIndex failed") + } + } + return nil +} + +func (p *Pusher) pushOffline(m *record.PushMsg, toIds []string) { + devs, err := p.s.dao.GetAllDevices(m.GetFromId()) + if err != nil || len(devs) == 0 { + p.s.log.Error().Err(err).Str("key", m.GetKey()).Str("from", m.GetFromId()).Msg("GetAllDevices failed") + return + } + nickname := devs[0].Username + + //offline push + for _, mid := range toIds { + err := p.pushAllDevice(m, nickname, mid) + if err != nil { + continue + } + } +} + +func (p *Pusher) pushAllDevice(m *record.PushMsg, nickname, mid string) error { + allDev, err := p.s.dao.GetAllDevices(mid) + if err != nil { + p.s.log.Error().Err(err).Str("key", m.GetKey()).Str("from", m.GetFromId()).Str("mid", mid).Msg("GetAllDevices failed") + return err + } + + for _, dev := range allDev { + if dev.IsEnabled && dev.DTUid == dev.Uid { + //需要推送 + pushMsg := &offlinepush.OffPushMsg{ + AppId: m.GetAppId(), + Device: offlinepush.Device(dev.DeviceType), + Title: nickname, + Content: "[你收到一条消息]", + Token: dev.DeviceToken, + ChannelType: int32(xproto.Channel_ToUser), + Target: m.GetFromId(), + Timeout: time.Now().Add(time.Minute * 7).Unix(), + } + b, err := proto.Marshal(pushMsg) + if err != nil { + p.s.log.Error().Err(err).Str("key", m.GetKey()).Str("from", m.GetFromId()).Str("appId", m.GetAppId()).Interface("toId", mid).Msg("Marshal pushMsg failed") + continue + } + err = p.s.dao.PublishOfflineMsg(context.TODO(), m.GetKey(), b) + if err != nil { + p.s.log.Error().Err(err).Str("key", m.GetKey()).Str("from", m.GetFromId()).Str("appId", m.GetAppId()).Interface("toId", mid).Msg("PublishOfflineMsg failed") + } + } + } + return nil +} diff --git a/service/record/pusher/service/service.go b/service/record/pusher/service/service.go new file mode 100644 index 0000000..da083e5 --- /dev/null +++ b/service/record/pusher/service/service.go @@ -0,0 +1,393 @@ +package service + +import ( + "context" + "errors" + "fmt" + "reflect" + "runtime" + "time" + + "github.com/Shopify/sarama" + "github.com/gammazero/workerpool" + "github.com/golang/protobuf/proto" + "github.com/opentracing/opentracing-go" + traceLog "github.com/opentracing/opentracing-go/log" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "github.com/uber/jaeger-client-go" + "gitlab.33.cn/chat/dtalk/pkg/naming" + "gitlab.33.cn/chat/dtalk/pkg/net/grpc" + "gitlab.33.cn/chat/dtalk/pkg/util" + answer "gitlab.33.cn/chat/dtalk/service/record/answer/api" + "gitlab.33.cn/chat/dtalk/service/record/kafka/consumer" + record "gitlab.33.cn/chat/dtalk/service/record/proto" + "gitlab.33.cn/chat/dtalk/service/record/pusher/config" + "gitlab.33.cn/chat/dtalk/service/record/pusher/dao" + "gitlab.33.cn/chat/dtalk/service/record/pusher/logH" + "gitlab.33.cn/chat/dtalk/service/record/pusher/model" + "gitlab.33.cn/chat/im-pkg/trace" + comet "gitlab.33.cn/chat/im/api/comet/grpc" + logic "gitlab.33.cn/chat/im/api/logic/grpc" + "gitlab.33.cn/chat/imparse" + "gitlab.33.cn/chat/imparse/chat" + xproto "gitlab.33.cn/chat/imparse/proto" + "google.golang.org/grpc/resolver" +) + +type Service struct { + log zerolog.Logger + dao *dao.Dao + lh *logH.LogHelper + cfg *config.Config + connConsumers map[string]*consumer.Consumer + receivedConsumers map[string]*consumer.Consumer + logicClient logic.LogicClient + + pusher Pusher + + answerClient *answer.Client + + workPoolConn *workerpool.WorkerPool + workPoolSend *workerpool.WorkerPool +} + +func New(c *config.Config) *Service { + s := &Service{ + log: log.Logger, + dao: dao.New(c), + lh: logH.NewLogHelper(dao.New(c)), + cfg: c, + connConsumers: consumer.NewKafkaConsumers( + fmt.Sprintf("goim-%s-topic", c.AppId), + fmt.Sprintf("goim-%s-conn-group", c.AppId), c.IMSub.Brokers, c.IMSub.Number), + receivedConsumers: consumer.NewKafkaConsumers( + fmt.Sprintf("received-%s-topic", c.AppId), + fmt.Sprintf("received-%s-group", c.AppId), c.RevSub.Brokers, c.RevSub.Number), + logicClient: newLogicClient(c), + answerClient: answer.New(c.AnswerRPCClient.RegAddrs, c.AnswerRPCClient.Schema, c.AnswerRPCClient.SrvName, time.Duration(c.AnswerRPCClient.Dial)), + workPoolConn: workerpool.New(c.IMSub.MaxWorker), + workPoolSend: workerpool.New(c.RevSub.MaxWorker), + } + s.pusher = Pusher{s: s} + return s +} + +func (s *Service) ListenMQ() { + s.log.Info().Int("numbers", len(s.connConsumers)).Msg("start serve mq consumers") + s.log.Info().Int("numbers", len(s.receivedConsumers)).Msg("start serve mq receivedConsumers") + for _, c := range s.connConsumers { + go c.Listen(s.asyncConn) + } + for _, c := range s.receivedConsumers { + go c.Listen(s.asyncSend) + } +} + +func (s *Service) Shutdown(ctx context.Context) { + down := make(chan struct{}) + go func() { + s.workPoolConn.StopWait() + s.workPoolSend.StopWait() + close(down) + }() + + select { + case <-ctx.Done(): + return + case <-down: + return + } +} + +func (s *Service) asyncConn(msg *sarama.ConsumerMessage) error { + s.workPoolConn.Submit(func() { + s.log.Debug().Str("topic", msg.Topic). + Bytes("key", msg.Key). + Int32("partition", msg.Partition). + Int64("offset", msg.Offset). + Msg("consume process") + err := s.consumeConn(msg) + if err != nil { + //s.log.Error().Err(err).Interface("msg", msg).Msg("proto.Unmarshal error") + } + }) + return nil +} + +func (s *Service) asyncSend(msg *sarama.ConsumerMessage) error { + s.workPoolSend.Submit(func() { + s.log.Debug().Str("topic", msg.Topic). + Bytes("key", msg.Key). + Int32("partition", msg.Partition). + Int64("offset", msg.Offset). + Msg("consume process") + err := s.consumePush(msg) + if err != nil { + //s.log.Error().Err(err).Interface("msg", msg).Msg("proto.Unmarshal error") + } + }) + return nil +} + +func newLogicClient(cfg *config.Config) logic.LogicClient { + rb := naming.NewResolver(cfg.LogicRPCClient.RegAddrs, cfg.LogicRPCClient.Schema) + resolver.Register(rb) + + addr := fmt.Sprintf("%s:///%s", cfg.LogicRPCClient.Schema, cfg.LogicRPCClient.SrvName) // "schema://[authority]/service" + fmt.Println("logic rpc client call addr:", addr) + + conn, err := grpc.NewGRPCConn(addr, time.Duration(cfg.LogicRPCClient.Dial)) + if err != nil { + panic(err) + } + return logic.NewLogicClient(conn) +} + +func (s *Service) consumeConn(msg *sarama.ConsumerMessage) (err error) { + bizMsg := new(logic.BizMsg) + if err = proto.Unmarshal(msg.Value, bizMsg); err != nil { + s.log.Error().Err(err).Bytes("bizMsg byte", msg.Value).Msg("logic.BizMsg proto.Unmarshal error") + return err + } + if bizMsg.AppId != s.cfg.AppId { + return model.ErrAppId + } + switch bizMsg.GetOp() { + case int32(comet.Op_Auth), int32(comet.Op_Disconnect), int32(comet.Op_ReceiveMsgReply), int32(comet.Op_SyncMsgReq): + spanName := fmt.Sprintf("consume %s:%s", msg.Topic, comet.Op(bizMsg.GetOp()).String()) + if err := s.consumeConnTraceAndLogIpt(spanName, bizMsg, s.DealConn); err != nil { + //TODO redo consume message + return err + } + default: + return model.ErrCustomNotSupport + } + return err +} + +func (s *Service) consumePush(msg *sarama.ConsumerMessage) (err error) { + bizMsg := new(record.PushMsg) + if err = proto.Unmarshal(msg.Value, bizMsg); err != nil { + s.log.Error().Err(err).Bytes("bizMsg byte", msg.Value).Msg("record.PushMsg proto.Unmarshal error") + return err + } + if bizMsg.AppId != s.cfg.AppId { + return model.ErrAppId + } + spanName := fmt.Sprintf("consume %s:%s", msg.Topic, "Push") + if err := s.consumePushTraceAndLogIpt(spanName, bizMsg, s.DealPush); err != nil { + //TODO redo consume message + return err + } + return nil +} + +func (s *Service) consumeConnTraceAndLogIpt(spanName string, bizMsg *logic.BizMsg, handler func(ctx context.Context, m *logic.BizMsg) error) error { + // trace + tracer := opentracing.GlobalTracer() + span := tracer.StartSpan(spanName) + defer span.Finish() + + traceId := "" + if jgSpan, ok := span.Context().(jaeger.SpanContext); ok { + traceId = jgSpan.TraceID().String() + } + //get global log instance + logger := s.log.With().Str(trace.TraceIDLogKey, traceId).Logger() + span.LogFields( + traceLog.String("AppId", bizMsg.GetAppId()), + traceLog.String("Uid", bizMsg.GetFromId()), + traceLog.String("ConnId", bizMsg.GetKey()), + ) + ctx := opentracing.ContextWithSpan(context.Background(), span) + ctx = logger.WithContext(ctx) + + err := handler(ctx, bizMsg) + if err != nil { + pointer := reflect.ValueOf(handler).Pointer() + funcName := runtime.FuncForPC(pointer).Name() + log.Error().Err(err).Msg(fmt.Sprintf("consume %s error", funcName)) + span.SetTag("ERROR", err) + return err + } + return nil +} + +func (s *Service) consumePushTraceAndLogIpt(spanName string, bizMsg *record.PushMsg, handler func(ctx context.Context, m *record.PushMsg) error) error { + // trace + tracer := opentracing.GlobalTracer() + span := tracer.StartSpan(spanName) + defer span.Finish() + + traceId := "" + if jgSpan, ok := span.Context().(jaeger.SpanContext); ok { + traceId = jgSpan.TraceID().String() + } + //get global log instance + logger := s.log.With().Str(trace.TraceIDLogKey, traceId).Logger() + span.LogFields( + traceLog.String("AppId", bizMsg.GetAppId()), + traceLog.String("Uid", bizMsg.GetFromId()), + traceLog.String("ConnId", bizMsg.GetKey()), + traceLog.Int64("Mid", bizMsg.GetMid()), + traceLog.String("Target", bizMsg.GetTarget()), + traceLog.String("Channel Type", imparse.Channel(bizMsg.GetType()).String()), + traceLog.String("Frame Type", bizMsg.GetFrameType()), + ) + ctx := opentracing.ContextWithSpan(context.Background(), span) + ctx = logger.WithContext(ctx) + + err := handler(ctx, bizMsg) + if err != nil { + pointer := reflect.ValueOf(handler).Pointer() + funcName := runtime.FuncForPC(pointer).Name() + log.Error().Err(err).Msg(fmt.Sprintf("consume %s error", funcName)) + span.SetTag("ERROR", err) + return err + } + return nil +} + +func (s *Service) parseDevice(m *logic.BizMsg) (*xproto.Login, error) { + var p comet.Proto + err := proto.Unmarshal(m.Msg, &p) + if err != nil { + return nil, err + } + var auth comet.AuthMsg + err = proto.Unmarshal(p.Body, &auth) + if err != nil { + return nil, err + } + if len(auth.Ext) == 0 { + return nil, errors.New("ext is nil") + } + var device xproto.Login + err = proto.Unmarshal(auth.Ext, &device) + if err != nil { + return nil, err + } + return &device, nil +} + +func (s *Service) DealConn(ctx context.Context, m *logic.BizMsg) error { + if m.AppId != s.cfg.AppId { + return model.ErrAppId + } + logger := zerolog.Ctx(ctx).With().Str("AppId", m.GetAppId()).Str("Uid", m.GetFromId()).Str("ConnId", m.GetKey()).Logger() + + switch m.Op { + case int32(comet.Op_Auth): + logger.Info().Msg("user login with key") + //将用户设备信息存入缓存 + device, err := s.parseDevice(m) + if err != nil { + logger.Error().Err(err).Msg("parseDevice failed") + } else { + err = s.dao.AddDeviceInfo(&model.DeviceInfo{ + Uid: m.FromId, + ConnectId: m.Key, + DeviceType: int32(device.Device), + Username: device.Username, + DeviceToken: device.DeviceToken, + IsEnabled: false, + AddTime: uint64(util.TimeNowUnixNano() / int64(time.Millisecond)), + }) + if err != nil { + logger.Error().Err(err).Msg("AddDeviceInfo failed") + } + } + err = s.dao.BatchPushPublish(ctx, m.Key, m.GetFromId()) + if err != nil { + logger.Error().Err(err).Msg("BatchPushPublish failed") + } + //连接群聊 + err = s.JoinGroups(ctx, m.GetFromId(), m.GetKey()) + if err != nil { + logger.Error().Err(err).Msg("JoinGroups failed") + } + case int32(comet.Op_Disconnect): + logger.Info().Msg("user logout with key") + err := s.dao.ClearConnSeq(m.Key) + if err != nil { + logger.Error().Err(err).Msg("ClearConnSeq failed") + } + err = s.dao.EnableDeviceInfo(m.GetFromId(), m.Key) + if err != nil { + logger.Error().Err(err).Msg("EnableDeviceInfo failed") + } + case int32(comet.Op_ReceiveMsgReply): + var p comet.Proto + err := proto.Unmarshal(m.Msg, &p) + if err != nil { + logger.Error().Err(err).Msg("unmarshal proto error") + return err + } + item, err := s.lh.GetLogsIndex(m.Key, p.Ack) + if err != nil { + logger.Error().Err(err).Int32("ack", p.Ack).Msg("GetConnSeqIndex failed") + return err + } + if item == nil { + //TODO print err + return nil + } + err = s.dao.MarkReadPublish(ctx, m.Key, m.GetFromId(), imparse.FrameType(item.Type), item.Logs) + if err != nil { + logger.Error().Err(err).Int32("ack", p.Ack).Msg("MarkReadPublish failed") + } + switch item.Type { + case string(chat.PrivateFrameType): + err := s.UniCastSignalReceived(ctx, item) + if err != nil { + logger.Error().Err(err).Int32("ack", p.Ack).Msg("UniCastSignalReceived failed") + return err + } + default: + } + case int32(comet.Op_SyncMsgReq): + //TODO disabled + return nil + var p comet.Proto + var pro comet.SyncMsg + err := proto.Unmarshal(m.Msg, &p) + if err != nil { + logger.Error().Err(err).Msg("unmarshal proto error") + return err + } + err = proto.Unmarshal(p.Body, &pro) + if err != nil { + logger.Error().Err(err).Interface("option", comet.Op_SyncMsgReq).Msg("Unmarshal failed") + break + } + err = s.dao.SyncPublish(ctx, m.Key, m.FromId, pro.LogId) + if err != nil { + logger.Error().Err(err).Int64("start", pro.LogId).Msg("SyncMsg failed") + break + } + default: + return model.ErrCustomNotSupport + } + return nil +} + +func (s *Service) DealPush(ctx context.Context, m *record.PushMsg) error { + logger := zerolog.Ctx(ctx).With().Str("AppId", m.GetAppId()).Str("Uid", m.GetFromId()).Str("ConnId", m.GetKey()). + Int64("Mid", m.GetMid()).Str("Target", m.GetTarget()).Int32("Push Type", m.GetType()).Str("Frame Type", m.GetFrameType()).Logger() + //sendB + var err error + switch m.GetType() { + case int32(imparse.UniCast): + err = s.pusher.UniCast(ctx, m) + case int32(imparse.GroupCast): + err = s.pusher.GroupCast(ctx, m) + default: + err = fmt.Errorf("push type %v undefined", m.GetType()) + } + if err != nil { + logger.Error().Stack().Err(err).Msg("DealPush error") + } + logger.Debug().Msg("pass DealSend") + return nil +} diff --git a/service/record/pusher/service/signal.go b/service/record/pusher/service/signal.go new file mode 100644 index 0000000..80f7678 --- /dev/null +++ b/service/record/pusher/service/signal.go @@ -0,0 +1,25 @@ +package service + +import ( + "context" + + "github.com/golang/protobuf/proto" + "gitlab.33.cn/chat/dtalk/service/record/pusher/logH" + xproto "gitlab.33.cn/chat/imparse/proto" +) + +//just socket push +func (s *Service) UniCastSignalReceived(ctx context.Context, item *logH.ConnSeqItem) error { + actionProto := &xproto.SignalReceived{ + Logs: item.Logs, + } + actionData, err := proto.Marshal(actionProto) + if err != nil { + return err + } + err = s.answerClient.UniCastSignal(ctx, xproto.SignalType_Received, item.Sender, actionData) + if err != nil { + return err + } + return nil +} diff --git a/service/record/pusher/version.go b/service/record/pusher/version.go new file mode 100644 index 0000000..1b1943d --- /dev/null +++ b/service/record/pusher/version.go @@ -0,0 +1,16 @@ +package pusher + +//var ( +// // The full version string +// Version = "0.6.2" +// +// // GitCommit is set with --ldflags "-X main.gitCommit=$(git rev-parse --short=8 HEAD)" +// GitCommit string +//) +// +//func GetVersion() string { +// if GitCommit != "" { +// return Version + "-" + GitCommit +// } +// return Version +//} diff --git a/service/record/store/CHANGELOG.md b/service/record/store/CHANGELOG.md new file mode 100644 index 0000000..c468a57 --- /dev/null +++ b/service/record/store/CHANGELOG.md @@ -0,0 +1,53 @@ +版本号`major.minor.patch`具体规则如下: +- major:主版本号,如有重大版本重构则该字段递增,通常各主版本间接口不兼容。 +- minor:次版本号,各次版本号间接口保持兼容,如有接口新增或优化则该字段递增。 +- patch:补丁号,如有功能改善或缺陷修复则该字段递增。 + +## version 0.7.5 2021_12_23 + +**数据库** +- `dtalk_msg_content` 新增 reference 字段 varchar 255 not null +- `dtalk_group_msg_content` 新增 reference 字段 varchar 255 not null + +**Feature** +- 消息新增引用字段 + + +## version 0.7.4 + +**Feature** +- 修改imparse本地依赖为远程仓库 2021_12_07_17_44_51 + +## version 0.7.3 + +**配置文件更新** + +所有 `[xxxRPCClient]` 增加 `RegAddrs = "127.0.0.1:2379"` 字段 + +**Feature** +- 重构 store v0.7.0-pre 2021.10.14 +- 更新 etcdv3.5.0 v0.7.2 +- 修复 source 为 null 的 bug v0.7.3 + +## version 0.6 + +**Feature** +- 新增获取聊天记录接口 @v0.6.0 2021.9.2 + + +## version 0.5 + +**Feature** +- 新增 3 中通知类型 +- 转账交易消息格式支持 @v0.5.3 2021.8.16 + + +## example x.x.x @yy.mm.dd + +**Feature** + +**Bug Fixes** + +**Improvement** + +**Breaking Change** diff --git a/service/record/store/Makefile b/service/record/store/Makefile new file mode 100644 index 0000000..4e2c823 --- /dev/null +++ b/service/record/store/Makefile @@ -0,0 +1,48 @@ +# golang1.9 or latest +# 1. make help +# 2. make dep +# 3. make build +# ... + +VERSION := $(shell echo $(shell cat cmd/main.go | grep "projectVersion =" | cut -d '=' -f2)) +APP_NAME := store +BUILD_DIR := build +APP := ${BUILD_DIR}/${APP_NAME}_v${VERSION} +PKG_NAME := ${APP_NAME}_v${VERSION} +PKG := ${PKG_NAME}.tar.gz + +main_path = "main" +go_version = $(shell go version | awk '{ print $3 }') +build_time = $(shell date "+%Y-%m-%d %H:%M:%S %Z") +git_commit = $(shell git rev-parse --short=10 HEAD) +flags := -ldflags "-X '${main_path}.goVersion=${go_version}' \ +-X '${main_path}.buildTime=${build_time}' \ +-X '${main_path}.gitCommit=${git_commit}' \ +-X 'google.golang.org/protobuf/reflect/protoregistry.conflictPolicy=warn'" + +.PHONY: clean build pkg + +clean: ## Remove previous build + @rm -rf ${BUILD_DIR} + @go clean + +build: #checkgofmt ## Build the binary file + GOOS=linux GOARCH=amd64 GO111MODULE=on GOPROXY=https://goproxy.cn,direct GOSUMDB="sum.golang.google.cn" go build -v $(flags) -o $(APP) cmd/main.go + +pkg: build ## Package + mkdir -p ${PKG_NAME}/bin + mkdir -p ${PKG_NAME}/etc + cp ${APP} ${PKG_NAME}/bin/ + cp etc/* ${PKG_NAME}/etc/ + tar zvcf ${PKG} ${PKG_NAME} + rm -rf ${PKG_NAME} + +run: + swag init -g server/http/http.go + go run $(LDGRPC) $(LDFLAGS) cmd/main.go -conf config/$(APP_NAME).toml + + +REMOTE_BIN_PATH := /opt/dtalk/srv/app/bin +upload: build + rsync -r $(APP) 107:$(REMOTE_BIN_PATH) + ssh 107 "cd $(REMOTE_BIN_PATH) && chmod 777 $(PKG_NAME) && ln -sf $(PKG_NAME) $(APP_NAME) && supervisorctl restart $(APP_NAME)" \ No newline at end of file diff --git a/service/record/store/api/api.pb.go b/service/record/store/api/api.pb.go new file mode 100644 index 0000000..ef7efc1 --- /dev/null +++ b/service/record/store/api/api.pb.go @@ -0,0 +1,758 @@ +// protoc -I=. -I=$GOPATH/src --go_out=plugins=grpc:. *.proto + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc v3.19.1 +// source: api.proto + +package store + +import ( + proto "gitlab.33.cn/chat/imparse/proto" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type DelRecordReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Tp proto.Channel `protobuf:"varint,1,opt,name=tp,proto3,enum=imparse.v1.Channel" json:"tp,omitempty"` + Mid int64 `protobuf:"varint,2,opt,name=mid,proto3" json:"mid,omitempty"` +} + +func (x *DelRecordReq) Reset() { + *x = DelRecordReq{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DelRecordReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DelRecordReq) ProtoMessage() {} + +func (x *DelRecordReq) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DelRecordReq.ProtoReflect.Descriptor instead. +func (*DelRecordReq) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{0} +} + +func (x *DelRecordReq) GetTp() proto.Channel { + if x != nil { + return x.Tp + } + return proto.Channel(0) +} + +func (x *DelRecordReq) GetMid() int64 { + if x != nil { + return x.Mid + } + return 0 +} + +type DelRecordReply struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *DelRecordReply) Reset() { + *x = DelRecordReply{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DelRecordReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DelRecordReply) ProtoMessage() {} + +func (x *DelRecordReply) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DelRecordReply.ProtoReflect.Descriptor instead. +func (*DelRecordReply) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{1} +} + +type GetRecordReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Tp proto.Channel `protobuf:"varint,1,opt,name=tp,proto3,enum=imparse.v1.Channel" json:"tp,omitempty"` + Mid int64 `protobuf:"varint,2,opt,name=mid,proto3" json:"mid,omitempty"` +} + +func (x *GetRecordReq) Reset() { + *x = GetRecordReq{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetRecordReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetRecordReq) ProtoMessage() {} + +func (x *GetRecordReq) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetRecordReq.ProtoReflect.Descriptor instead. +func (*GetRecordReq) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{2} +} + +func (x *GetRecordReq) GetTp() proto.Channel { + if x != nil { + return x.Tp + } + return proto.Channel(0) +} + +func (x *GetRecordReq) GetMid() int64 { + if x != nil { + return x.Mid + } + return 0 +} + +type GetRecordReply struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Mid string `protobuf:"bytes,1,opt,name=mid,proto3" json:"mid,omitempty"` + Seq string `protobuf:"bytes,2,opt,name=seq,proto3" json:"seq,omitempty"` + SenderId string `protobuf:"bytes,3,opt,name=senderId,proto3" json:"senderId,omitempty"` + ReceiverId string `protobuf:"bytes,4,opt,name=receiverId,proto3" json:"receiverId,omitempty"` + MsgType uint32 `protobuf:"varint,5,opt,name=msgType,proto3" json:"msgType,omitempty"` + Content string `protobuf:"bytes,6,opt,name=content,proto3" json:"content,omitempty"` + CreateTime uint64 `protobuf:"varint,7,opt,name=createTime,proto3" json:"createTime,omitempty"` + Source string `protobuf:"bytes,8,opt,name=source,proto3" json:"source,omitempty"` +} + +func (x *GetRecordReply) Reset() { + *x = GetRecordReply{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetRecordReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetRecordReply) ProtoMessage() {} + +func (x *GetRecordReply) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetRecordReply.ProtoReflect.Descriptor instead. +func (*GetRecordReply) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{3} +} + +func (x *GetRecordReply) GetMid() string { + if x != nil { + return x.Mid + } + return "" +} + +func (x *GetRecordReply) GetSeq() string { + if x != nil { + return x.Seq + } + return "" +} + +func (x *GetRecordReply) GetSenderId() string { + if x != nil { + return x.SenderId + } + return "" +} + +func (x *GetRecordReply) GetReceiverId() string { + if x != nil { + return x.ReceiverId + } + return "" +} + +func (x *GetRecordReply) GetMsgType() uint32 { + if x != nil { + return x.MsgType + } + return 0 +} + +func (x *GetRecordReply) GetContent() string { + if x != nil { + return x.Content + } + return "" +} + +func (x *GetRecordReply) GetCreateTime() uint64 { + if x != nil { + return x.CreateTime + } + return 0 +} + +func (x *GetRecordReply) GetSource() string { + if x != nil { + return x.Source + } + return "" +} + +type GetRecordsAfterMidReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Tp proto.Channel `protobuf:"varint,1,opt,name=tp,proto3,enum=imparse.v1.Channel" json:"tp,omitempty"` + Mid int64 `protobuf:"varint,2,opt,name=mid,proto3" json:"mid,omitempty"` + From string `protobuf:"bytes,3,opt,name=from,proto3" json:"from,omitempty"` + Target string `protobuf:"bytes,4,opt,name=target,proto3" json:"target,omitempty"` + Count int64 `protobuf:"varint,5,opt,name=count,proto3" json:"count,omitempty"` +} + +func (x *GetRecordsAfterMidReq) Reset() { + *x = GetRecordsAfterMidReq{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetRecordsAfterMidReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetRecordsAfterMidReq) ProtoMessage() {} + +func (x *GetRecordsAfterMidReq) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetRecordsAfterMidReq.ProtoReflect.Descriptor instead. +func (*GetRecordsAfterMidReq) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{4} +} + +func (x *GetRecordsAfterMidReq) GetTp() proto.Channel { + if x != nil { + return x.Tp + } + return proto.Channel(0) +} + +func (x *GetRecordsAfterMidReq) GetMid() int64 { + if x != nil { + return x.Mid + } + return 0 +} + +func (x *GetRecordsAfterMidReq) GetFrom() string { + if x != nil { + return x.From + } + return "" +} + +func (x *GetRecordsAfterMidReq) GetTarget() string { + if x != nil { + return x.Target + } + return "" +} + +func (x *GetRecordsAfterMidReq) GetCount() int64 { + if x != nil { + return x.Count + } + return 0 +} + +type GetRecordsAfterMidReply struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Records []*GetRecordReply `protobuf:"bytes,1,rep,name=records,proto3" json:"records,omitempty"` +} + +func (x *GetRecordsAfterMidReply) Reset() { + *x = GetRecordsAfterMidReply{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetRecordsAfterMidReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetRecordsAfterMidReply) ProtoMessage() {} + +func (x *GetRecordsAfterMidReply) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetRecordsAfterMidReply.ProtoReflect.Descriptor instead. +func (*GetRecordsAfterMidReply) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{5} +} + +func (x *GetRecordsAfterMidReply) GetRecords() []*GetRecordReply { + if x != nil { + return x.Records + } + return nil +} + +type AddRecordFocusReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Uid string `protobuf:"bytes,1,opt,name=uid,proto3" json:"uid,omitempty"` + Mid int64 `protobuf:"varint,2,opt,name=mid,proto3" json:"mid,omitempty"` + Time uint64 `protobuf:"varint,3,opt,name=time,proto3" json:"time,omitempty"` +} + +func (x *AddRecordFocusReq) Reset() { + *x = AddRecordFocusReq{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AddRecordFocusReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AddRecordFocusReq) ProtoMessage() {} + +func (x *AddRecordFocusReq) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AddRecordFocusReq.ProtoReflect.Descriptor instead. +func (*AddRecordFocusReq) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{6} +} + +func (x *AddRecordFocusReq) GetUid() string { + if x != nil { + return x.Uid + } + return "" +} + +func (x *AddRecordFocusReq) GetMid() int64 { + if x != nil { + return x.Mid + } + return 0 +} + +func (x *AddRecordFocusReq) GetTime() uint64 { + if x != nil { + return x.Time + } + return 0 +} + +type AddRecordFocusReply struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + CurrentNum int32 `protobuf:"varint,1,opt,name=currentNum,proto3" json:"currentNum,omitempty"` +} + +func (x *AddRecordFocusReply) Reset() { + *x = AddRecordFocusReply{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AddRecordFocusReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AddRecordFocusReply) ProtoMessage() {} + +func (x *AddRecordFocusReply) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AddRecordFocusReply.ProtoReflect.Descriptor instead. +func (*AddRecordFocusReply) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{7} +} + +func (x *AddRecordFocusReply) GetCurrentNum() int32 { + if x != nil { + return x.CurrentNum + } + return 0 +} + +var File_api_proto protoreflect.FileDescriptor + +var file_api_proto_rawDesc = []byte{ + 0x0a, 0x09, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x64, 0x74, 0x61, + 0x6c, 0x6b, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x1a, 0x28, 0x67, 0x69, 0x74, 0x6c, 0x61, 0x62, + 0x2e, 0x33, 0x33, 0x2e, 0x63, 0x6e, 0x2f, 0x63, 0x68, 0x61, 0x74, 0x2f, 0x69, 0x6d, 0x70, 0x61, + 0x72, 0x73, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x31, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x22, 0x45, 0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, + 0x65, 0x71, 0x12, 0x23, 0x0a, 0x02, 0x74, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x13, + 0x2e, 0x69, 0x6d, 0x70, 0x61, 0x72, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x52, 0x02, 0x74, 0x70, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x69, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x6d, 0x69, 0x64, 0x22, 0x10, 0x0a, 0x0e, 0x44, 0x65, 0x6c, + 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x45, 0x0a, 0x0c, 0x47, + 0x65, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x12, 0x23, 0x0a, 0x02, 0x74, + 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x13, 0x2e, 0x69, 0x6d, 0x70, 0x61, 0x72, 0x73, + 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x02, 0x74, 0x70, + 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x6d, + 0x69, 0x64, 0x22, 0xdc, 0x01, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, + 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x6d, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x73, 0x65, 0x71, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x73, 0x65, 0x71, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x6e, + 0x64, 0x65, 0x72, 0x49, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65, 0x6e, + 0x64, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, + 0x72, 0x49, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x63, 0x65, 0x69, + 0x76, 0x65, 0x72, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x73, 0x67, 0x54, 0x79, 0x70, 0x65, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x6d, 0x73, 0x67, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x63, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x22, 0x90, 0x01, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, + 0x41, 0x66, 0x74, 0x65, 0x72, 0x4d, 0x69, 0x64, 0x52, 0x65, 0x71, 0x12, 0x23, 0x0a, 0x02, 0x74, + 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x13, 0x2e, 0x69, 0x6d, 0x70, 0x61, 0x72, 0x73, + 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x02, 0x74, 0x70, + 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x6d, + 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x14, + 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x50, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x72, + 0x64, 0x73, 0x41, 0x66, 0x74, 0x65, 0x72, 0x4d, 0x69, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, + 0x35, 0x0a, 0x07, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x1b, 0x2e, 0x64, 0x74, 0x61, 0x6c, 0x6b, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x47, + 0x65, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x52, 0x07, 0x72, + 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x22, 0x4b, 0x0a, 0x11, 0x41, 0x64, 0x64, 0x52, 0x65, 0x63, + 0x6f, 0x72, 0x64, 0x46, 0x6f, 0x63, 0x75, 0x73, 0x52, 0x65, 0x71, 0x12, 0x10, 0x0a, 0x03, 0x75, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x69, 0x64, 0x12, 0x10, 0x0a, + 0x03, 0x6d, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x6d, 0x69, 0x64, 0x12, + 0x12, 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x74, + 0x69, 0x6d, 0x65, 0x22, 0x35, 0x0a, 0x13, 0x41, 0x64, 0x64, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, + 0x46, 0x6f, 0x63, 0x75, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x75, + 0x72, 0x72, 0x65, 0x6e, 0x74, 0x4e, 0x75, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, + 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x4e, 0x75, 0x6d, 0x32, 0xc5, 0x02, 0x0a, 0x05, 0x53, + 0x74, 0x6f, 0x72, 0x65, 0x12, 0x43, 0x0a, 0x09, 0x44, 0x65, 0x6c, 0x52, 0x65, 0x63, 0x6f, 0x72, + 0x64, 0x12, 0x19, 0x2e, 0x64, 0x74, 0x61, 0x6c, 0x6b, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, + 0x44, 0x65, 0x6c, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x1a, 0x1b, 0x2e, 0x64, + 0x74, 0x61, 0x6c, 0x6b, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x44, 0x65, 0x6c, 0x52, 0x65, + 0x63, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x43, 0x0a, 0x09, 0x47, 0x65, 0x74, + 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x19, 0x2e, 0x64, 0x74, 0x61, 0x6c, 0x6b, 0x2e, 0x73, + 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x65, + 0x71, 0x1a, 0x1b, 0x2e, 0x64, 0x74, 0x61, 0x6c, 0x6b, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, + 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x52, + 0x0a, 0x0e, 0x41, 0x64, 0x64, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x46, 0x6f, 0x63, 0x75, 0x73, + 0x12, 0x1e, 0x2e, 0x64, 0x74, 0x61, 0x6c, 0x6b, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x41, + 0x64, 0x64, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x46, 0x6f, 0x63, 0x75, 0x73, 0x52, 0x65, 0x71, + 0x1a, 0x20, 0x2e, 0x64, 0x74, 0x61, 0x6c, 0x6b, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x41, + 0x64, 0x64, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x46, 0x6f, 0x63, 0x75, 0x73, 0x52, 0x65, 0x70, + 0x6c, 0x79, 0x12, 0x5e, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, + 0x41, 0x66, 0x74, 0x65, 0x72, 0x4d, 0x69, 0x64, 0x12, 0x22, 0x2e, 0x64, 0x74, 0x61, 0x6c, 0x6b, + 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, + 0x73, 0x41, 0x66, 0x74, 0x65, 0x72, 0x4d, 0x69, 0x64, 0x52, 0x65, 0x71, 0x1a, 0x24, 0x2e, 0x64, + 0x74, 0x61, 0x6c, 0x6b, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, + 0x63, 0x6f, 0x72, 0x64, 0x73, 0x41, 0x66, 0x74, 0x65, 0x72, 0x4d, 0x69, 0x64, 0x52, 0x65, 0x70, + 0x6c, 0x79, 0x42, 0x2e, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x6c, 0x61, 0x62, 0x2e, 0x33, 0x33, 0x2e, + 0x63, 0x6e, 0x2f, 0x63, 0x68, 0x61, 0x74, 0x2f, 0x64, 0x74, 0x61, 0x6c, 0x6b, 0x2f, 0x73, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x2f, 0x73, 0x74, 0x6f, + 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_api_proto_rawDescOnce sync.Once + file_api_proto_rawDescData = file_api_proto_rawDesc +) + +func file_api_proto_rawDescGZIP() []byte { + file_api_proto_rawDescOnce.Do(func() { + file_api_proto_rawDescData = protoimpl.X.CompressGZIP(file_api_proto_rawDescData) + }) + return file_api_proto_rawDescData +} + +var file_api_proto_msgTypes = make([]protoimpl.MessageInfo, 8) +var file_api_proto_goTypes = []interface{}{ + (*DelRecordReq)(nil), // 0: dtalk.store.DelRecordReq + (*DelRecordReply)(nil), // 1: dtalk.store.DelRecordReply + (*GetRecordReq)(nil), // 2: dtalk.store.GetRecordReq + (*GetRecordReply)(nil), // 3: dtalk.store.GetRecordReply + (*GetRecordsAfterMidReq)(nil), // 4: dtalk.store.GetRecordsAfterMidReq + (*GetRecordsAfterMidReply)(nil), // 5: dtalk.store.GetRecordsAfterMidReply + (*AddRecordFocusReq)(nil), // 6: dtalk.store.AddRecordFocusReq + (*AddRecordFocusReply)(nil), // 7: dtalk.store.AddRecordFocusReply + (proto.Channel)(0), // 8: imparse.v1.Channel +} +var file_api_proto_depIdxs = []int32{ + 8, // 0: dtalk.store.DelRecordReq.tp:type_name -> imparse.v1.Channel + 8, // 1: dtalk.store.GetRecordReq.tp:type_name -> imparse.v1.Channel + 8, // 2: dtalk.store.GetRecordsAfterMidReq.tp:type_name -> imparse.v1.Channel + 3, // 3: dtalk.store.GetRecordsAfterMidReply.records:type_name -> dtalk.store.GetRecordReply + 0, // 4: dtalk.store.Store.DelRecord:input_type -> dtalk.store.DelRecordReq + 2, // 5: dtalk.store.Store.GetRecord:input_type -> dtalk.store.GetRecordReq + 6, // 6: dtalk.store.Store.AddRecordFocus:input_type -> dtalk.store.AddRecordFocusReq + 4, // 7: dtalk.store.Store.GetRecordsAfterMid:input_type -> dtalk.store.GetRecordsAfterMidReq + 1, // 8: dtalk.store.Store.DelRecord:output_type -> dtalk.store.DelRecordReply + 3, // 9: dtalk.store.Store.GetRecord:output_type -> dtalk.store.GetRecordReply + 7, // 10: dtalk.store.Store.AddRecordFocus:output_type -> dtalk.store.AddRecordFocusReply + 5, // 11: dtalk.store.Store.GetRecordsAfterMid:output_type -> dtalk.store.GetRecordsAfterMidReply + 8, // [8:12] is the sub-list for method output_type + 4, // [4:8] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name +} + +func init() { file_api_proto_init() } +func file_api_proto_init() { + if File_api_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_api_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DelRecordReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DelRecordReply); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetRecordReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetRecordReply); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetRecordsAfterMidReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetRecordsAfterMidReply); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AddRecordFocusReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AddRecordFocusReply); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_api_proto_rawDesc, + NumEnums: 0, + NumMessages: 8, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_api_proto_goTypes, + DependencyIndexes: file_api_proto_depIdxs, + MessageInfos: file_api_proto_msgTypes, + }.Build() + File_api_proto = out.File + file_api_proto_rawDesc = nil + file_api_proto_goTypes = nil + file_api_proto_depIdxs = nil +} diff --git a/service/record/store/api/api.proto b/service/record/store/api/api.proto new file mode 100644 index 0000000..8d0ff92 --- /dev/null +++ b/service/record/store/api/api.proto @@ -0,0 +1,60 @@ +// protoc -I=. -I=$GOPATH/src --go_out=plugins=grpc:. *.proto +syntax = "proto3"; + +package dtalk.store; +option go_package = "gitlab.33.cn/chat/dtalk/service/record/store"; + +import "gitlab.33.cn/chat/imparse/proto/v1.proto"; + +message DelRecordReq { + imparse.v1.Channel tp = 1; + int64 mid = 2; +} + +message DelRecordReply { +} + +message GetRecordReq { + imparse.v1.Channel tp = 1; + int64 mid = 2; +} + +message GetRecordReply { + string mid = 1; + string seq = 2; + string senderId = 3; + string receiverId = 4; + uint32 msgType = 5; + string content = 6; + uint64 createTime = 7; + string source = 8; +} + +message GetRecordsAfterMidReq { + imparse.v1.Channel tp = 1; + int64 mid = 2; + string from = 3; + string target = 4; + int64 count = 5; +} + +message GetRecordsAfterMidReply { + repeated GetRecordReply records = 1; +} + +message AddRecordFocusReq { + string uid = 1; + int64 mid = 2; + uint64 time = 3; +} + +message AddRecordFocusReply { + int32 currentNum = 1; +} + +service Store { + rpc DelRecord(DelRecordReq) returns (DelRecordReply); + rpc GetRecord(GetRecordReq) returns (GetRecordReply); + rpc AddRecordFocus(AddRecordFocusReq) returns (AddRecordFocusReply); + rpc GetRecordsAfterMid(GetRecordsAfterMidReq) returns (GetRecordsAfterMidReply); +} \ No newline at end of file diff --git a/service/record/store/api/api_grpc.pb.go b/service/record/store/api/api_grpc.pb.go new file mode 100644 index 0000000..68d1e13 --- /dev/null +++ b/service/record/store/api/api_grpc.pb.go @@ -0,0 +1,209 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. + +package store + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// StoreClient is the client API for Store service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type StoreClient interface { + DelRecord(ctx context.Context, in *DelRecordReq, opts ...grpc.CallOption) (*DelRecordReply, error) + GetRecord(ctx context.Context, in *GetRecordReq, opts ...grpc.CallOption) (*GetRecordReply, error) + AddRecordFocus(ctx context.Context, in *AddRecordFocusReq, opts ...grpc.CallOption) (*AddRecordFocusReply, error) + GetRecordsAfterMid(ctx context.Context, in *GetRecordsAfterMidReq, opts ...grpc.CallOption) (*GetRecordsAfterMidReply, error) +} + +type storeClient struct { + cc grpc.ClientConnInterface +} + +func NewStoreClient(cc grpc.ClientConnInterface) StoreClient { + return &storeClient{cc} +} + +func (c *storeClient) DelRecord(ctx context.Context, in *DelRecordReq, opts ...grpc.CallOption) (*DelRecordReply, error) { + out := new(DelRecordReply) + err := c.cc.Invoke(ctx, "/dtalk.store.Store/DelRecord", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *storeClient) GetRecord(ctx context.Context, in *GetRecordReq, opts ...grpc.CallOption) (*GetRecordReply, error) { + out := new(GetRecordReply) + err := c.cc.Invoke(ctx, "/dtalk.store.Store/GetRecord", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *storeClient) AddRecordFocus(ctx context.Context, in *AddRecordFocusReq, opts ...grpc.CallOption) (*AddRecordFocusReply, error) { + out := new(AddRecordFocusReply) + err := c.cc.Invoke(ctx, "/dtalk.store.Store/AddRecordFocus", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *storeClient) GetRecordsAfterMid(ctx context.Context, in *GetRecordsAfterMidReq, opts ...grpc.CallOption) (*GetRecordsAfterMidReply, error) { + out := new(GetRecordsAfterMidReply) + err := c.cc.Invoke(ctx, "/dtalk.store.Store/GetRecordsAfterMid", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// StoreServer is the server API for Store service. +// All implementations must embed UnimplementedStoreServer +// for forward compatibility +type StoreServer interface { + DelRecord(context.Context, *DelRecordReq) (*DelRecordReply, error) + GetRecord(context.Context, *GetRecordReq) (*GetRecordReply, error) + AddRecordFocus(context.Context, *AddRecordFocusReq) (*AddRecordFocusReply, error) + GetRecordsAfterMid(context.Context, *GetRecordsAfterMidReq) (*GetRecordsAfterMidReply, error) + mustEmbedUnimplementedStoreServer() +} + +// UnimplementedStoreServer must be embedded to have forward compatible implementations. +type UnimplementedStoreServer struct { +} + +func (UnimplementedStoreServer) DelRecord(context.Context, *DelRecordReq) (*DelRecordReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method DelRecord not implemented") +} +func (UnimplementedStoreServer) GetRecord(context.Context, *GetRecordReq) (*GetRecordReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetRecord not implemented") +} +func (UnimplementedStoreServer) AddRecordFocus(context.Context, *AddRecordFocusReq) (*AddRecordFocusReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method AddRecordFocus not implemented") +} +func (UnimplementedStoreServer) GetRecordsAfterMid(context.Context, *GetRecordsAfterMidReq) (*GetRecordsAfterMidReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetRecordsAfterMid not implemented") +} +func (UnimplementedStoreServer) mustEmbedUnimplementedStoreServer() {} + +// UnsafeStoreServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to StoreServer will +// result in compilation errors. +type UnsafeStoreServer interface { + mustEmbedUnimplementedStoreServer() +} + +func RegisterStoreServer(s grpc.ServiceRegistrar, srv StoreServer) { + s.RegisterService(&Store_ServiceDesc, srv) +} + +func _Store_DelRecord_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DelRecordReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StoreServer).DelRecord(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.store.Store/DelRecord", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StoreServer).DelRecord(ctx, req.(*DelRecordReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Store_GetRecord_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetRecordReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StoreServer).GetRecord(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.store.Store/GetRecord", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StoreServer).GetRecord(ctx, req.(*GetRecordReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Store_AddRecordFocus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AddRecordFocusReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StoreServer).AddRecordFocus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.store.Store/AddRecordFocus", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StoreServer).AddRecordFocus(ctx, req.(*AddRecordFocusReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Store_GetRecordsAfterMid_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetRecordsAfterMidReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StoreServer).GetRecordsAfterMid(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dtalk.store.Store/GetRecordsAfterMid", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StoreServer).GetRecordsAfterMid(ctx, req.(*GetRecordsAfterMidReq)) + } + return interceptor(ctx, in, info, handler) +} + +// Store_ServiceDesc is the grpc.ServiceDesc for Store service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Store_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "dtalk.store.Store", + HandlerType: (*StoreServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "DelRecord", + Handler: _Store_DelRecord_Handler, + }, + { + MethodName: "GetRecord", + Handler: _Store_GetRecord_Handler, + }, + { + MethodName: "AddRecordFocus", + Handler: _Store_AddRecordFocus_Handler, + }, + { + MethodName: "GetRecordsAfterMid", + Handler: _Store_GetRecordsAfterMid_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "api.proto", +} diff --git a/service/record/store/api/client.go b/service/record/store/api/client.go new file mode 100644 index 0000000..e7a6c53 --- /dev/null +++ b/service/record/store/api/client.go @@ -0,0 +1,86 @@ +package store + +import ( + "context" + "fmt" + "time" + + "gitlab.33.cn/chat/dtalk/pkg/naming" + xgrpc "gitlab.33.cn/chat/dtalk/pkg/net/grpc" + "gitlab.33.cn/chat/dtalk/service/record/store/model" + "gitlab.33.cn/chat/im-pkg/trace" + xproto "gitlab.33.cn/chat/imparse/proto" + "google.golang.org/grpc" + "google.golang.org/grpc/resolver" +) + +type Client struct { + client StoreClient +} + +func New(etcdAddr, schema, srvName string, dial time.Duration) *Client { + rb := naming.NewResolver(etcdAddr, schema) + resolver.Register(rb) + + addr := fmt.Sprintf("%s:///%s", schema, srvName) // "schema://[authority]/service" + fmt.Println("answer rpc client call addr:", addr) + + conn, err := xgrpc.NewGRPCConnWithOpts(addr, dial, grpc.WithUnaryInterceptor(trace.OpentracingClientInterceptor)) + if err != nil { + panic(err) + } + return &Client{ + client: NewStoreClient(conn), + } +} + +func (c *Client) DelRecord(ctx context.Context, tp xproto.Channel, mid int64) error { + in := &DelRecordReq{ + Tp: tp, + Mid: mid, + } + _, err := c.client.DelRecord(ctx, in) + return err +} + +func (c *Client) GetRecord(ctx context.Context, tp xproto.Channel, mid int64) (*model.MsgContent, error) { + in := &GetRecordReq{ + Tp: tp, + Mid: mid, + } + reply, err := c.client.GetRecord(ctx, in) + if reply == nil { + return nil, model.ErrRecordNotFind + } + return &model.MsgContent{ + Mid: reply.Mid, + Seq: reply.Seq, + SenderId: reply.SenderId, + ReceiverId: reply.ReceiverId, + MsgType: reply.MsgType, + Content: reply.Content, + CreateTime: reply.CreateTime, + Source: reply.Source, + }, err +} + +func (c *Client) AddRecordFocus(ctx context.Context, uid string, mid int64, time uint64) (int32, error) { + in := &AddRecordFocusReq{ + Uid: uid, + Mid: mid, + Time: time, + } + reply, err := c.client.AddRecordFocus(ctx, in) + if reply == nil { + return 0, model.ErrRecordNotFind + } + return reply.CurrentNum, err +} + +func (c *Client) GetRecordsAfterMid(ctx context.Context, req *GetRecordsAfterMidReq) (*GetRecordsAfterMidReply, error) { + reply, err := c.client.GetRecordsAfterMid(ctx, req) + if reply == nil { + return nil, model.ErrRecordNotFind + } + return reply, err +} diff --git a/service/record/store/api/create.sh b/service/record/store/api/create.sh new file mode 100644 index 0000000..0e5dbf9 --- /dev/null +++ b/service/record/store/api/create.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +protoc -I. -I$GOPATH/src \ + --go_out=. --go_opt=paths=source_relative \ + --go-grpc_out=. --go-grpc_opt=paths=source_relative \ + *.proto \ No newline at end of file diff --git a/service/record/store/bale/bale.go b/service/record/store/bale/bale.go new file mode 100644 index 0000000..270b78a --- /dev/null +++ b/service/record/store/bale/bale.go @@ -0,0 +1,15 @@ +package bale + +import xproto "gitlab.33.cn/chat/imparse/proto" + +type Item interface { + Data() []byte +} + +type Bale struct { + ss []xproto.Proto +} + +func Load(item Item) { + +} diff --git a/service/record/store/cmd/main.go b/service/record/store/cmd/main.go new file mode 100644 index 0000000..9bf8ac3 --- /dev/null +++ b/service/record/store/cmd/main.go @@ -0,0 +1,131 @@ +package main + +import ( + "context" + "flag" + "fmt" + xlog "gitlab.33.cn/chat/dtalk/pkg/log" + "net" + "os" + "os/signal" + "syscall" + "time" + + "github.com/Terry-Mao/goim/pkg/ip" + "github.com/opentracing/opentracing-go" + "github.com/rs/zerolog/log" + "gitlab.33.cn/chat/dtalk/pkg/naming" + "gitlab.33.cn/chat/dtalk/service/record/store/config" + "gitlab.33.cn/chat/dtalk/service/record/store/server/grpc" + "gitlab.33.cn/chat/dtalk/service/record/store/service" + "gitlab.33.cn/chat/im-pkg/trace" +) + +const srvName = "store" + +var ( + // projectVersion 项目版本 + projectVersion = "0.7.6" + // goVersion go版本 + goVersion = "" + // gitCommit git提交commit id + gitCommit = "" + // buildTime 编译时间 + buildTime = "" + + isShowVersion = flag.Bool("v", false, "show project version") +) + +// showVersion 显示项目版本信息 +func showVersion(isShow bool) { + if isShow { + fmt.Printf("Project: %s\n", srvName) + fmt.Printf(" Version: %s\n", projectVersion) + fmt.Printf(" Go Version: %s\n", goVersion) + fmt.Printf(" Git Commit: %s\n", gitCommit) + fmt.Printf(" Build Time: %s\n", buildTime) + os.Exit(0) + } +} + +// @Title 聊天单模块集成测试 +// @Version 0.1 +// @Description +// @SecurityDefinitions.ApiKey ApiKeyAuth +// @In header +// @Name Authorization +// @BasePath / +func main() { + flag.Parse() + showVersion(*isShowVersion) + + if err := config.Init(); err != nil { + panic(err) + } + //log init + logger, err := xlog.Init(config.Conf.Log) + if err != nil { + panic(err) + } + // set global log instance + log.Logger = logger.With().Str("service", srvName).Logger() + log.Info(). + Str("AppId", config.Conf.AppId). + Str("Env", config.Conf.Env). + Interface("Reg", config.Conf.Reg). + Interface("MySQL", config.Conf.MySQL). + Interface("Redis", config.Conf.Redis). + Interface("PusherRPCClient", config.Conf.PusherRPCClient). + Interface("RevSub", config.Conf.RevSub). + Interface("StoreSub", config.Conf.StoreSub). + Msg("config info") + + // trace init + tracer, tracerCloser := trace.Init(srvName, config.Conf.Trace) + //不然后续不会有Jaeger实例 + opentracing.SetGlobalTracer(tracer) + + // service init + svc := service.New(config.Conf) + rpc := grpc.New(config.Conf.GRPCServer, svc) + svc.ListenMQ() + + // register server + _, port, _ := net.SplitHostPort(config.Conf.GRPCServer.Addr) + addr := fmt.Sprintf("%s:%s", ip.InternalIP(), port) + if err := naming.Register(config.Conf.Reg.RegAddrs, config.Conf.Reg.SrvName, addr, config.Conf.Reg.Schema, 15); err != nil { + panic(err) + } + fmt.Println("register ok") + + // init signal + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT) + for { + s := <-c + log.Info().Str("signal", s.String()).Msg("service get a signal") + switch s { + case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT: + time.Sleep(time.Second * 2) + log.Info().Str("name", srvName).Msg("server exit") + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := rpc.Shutdown(ctx); err != nil { + log.Error().Err(err).Msg("rpc.Shutdown") + } + if err := naming.UnRegister(config.Conf.Reg.SrvName, addr, config.Conf.Reg.Schema); err != nil { + log.Error().Err(err).Msg("naming.UnRegister") + } + if err := tracerCloser.Close(); err != nil { + log.Error().Err(err).Msg("tracer close failed") + } + svc.Shutdown(ctx) + return + case syscall.SIGHUP: + // TODO reload + default: + return + } + } +} diff --git a/service/record/store/config/config.go b/service/record/store/config/config.go new file mode 100644 index 0000000..4471c17 --- /dev/null +++ b/service/record/store/config/config.go @@ -0,0 +1,186 @@ +package config + +import ( + "flag" + "github.com/uber/jaeger-client-go" + traceConfig "github.com/uber/jaeger-client-go/config" + xlog "gitlab.33.cn/chat/dtalk/pkg/log" + "os" + "time" + + "github.com/BurntSushi/toml" + "gitlab.33.cn/chat/dtalk/pkg/net/grpc" + xgrpc "gitlab.33.cn/chat/dtalk/pkg/net/grpc" + xtime "gitlab.33.cn/chat/dtalk/pkg/time" +) + +var ( + confPath string + regAddrs string + env string + + Conf *Config +) + +func init() { + var ( + defAddrs = os.Getenv("REGADDRS") + defEnv = os.Getenv("DTALKENV") + ) + flag.StringVar(&confPath, "conf", "store.toml", "default config path.") + flag.StringVar(®Addrs, "reg", defAddrs, "etcd register addrs. eg:127.0.0.1:2379") + flag.StringVar(&env, "env", defEnv, "service runtime environment") +} + +// Init init config. +func Init() (err error) { + Conf = Default() + _, err = toml.DecodeFile(confPath, &Conf) + return +} + +func Default() *Config { + return &Config{ + AppId: "dtalk", + Engine: "standard", + Env: env, + SyncCache: true, + Log: xlog.Config{ + Level: xlog.DebugLevel, + Mode: xlog.ConsoleMode, + Path: "", + Display: xlog.JsonDisplay, + }, + Trace: traceConfig.Configuration{ + ServiceName: "store", + Gen128Bit: true, + Sampler: &traceConfig.SamplerConfig{ + Type: jaeger.SamplerTypeConst, + Param: 1, + }, + Reporter: &traceConfig.ReporterConfig{ + LogSpans: true, + LocalAgentHostPort: "127.0.0.1:6831", + }, + }, + Reg: &Reg{ + Schema: "dtalk", + SrvName: "store", + RegAddrs: regAddrs, + }, + GRPCServer: &xgrpc.ServerConfig{ + Network: "tcp", + Addr: ":30005", + Timeout: xtime.Duration(time.Second), + KeepAliveMaxConnectionIdle: xtime.Duration(time.Second * 60), + KeepAliveMaxConnectionAge: xtime.Duration(time.Hour * 2), + KeepAliveMaxMaxConnectionAgeGrace: xtime.Duration(time.Second * 20), + KeepAliveTime: xtime.Duration(time.Second * 60), + KeepAliveTimeout: xtime.Duration(time.Second * 20), + }, + MySQL: &MySQL{ + Host: "127.0.0.1", + Port: 3306, + User: "root", + Pwd: "123456", + Db: "dtalk", + }, + Redis: &Redis{ + Network: "tcp", + Addr: "127.0.0.1:6379", + Auth: "", + Active: 60000, + Idle: 1024, + DialTimeout: xtime.Duration(200 * time.Millisecond), + ReadTimeout: xtime.Duration(500 * time.Millisecond), + WriteTimeout: xtime.Duration(500 * time.Millisecond), + IdleTimeout: xtime.Duration(120 * time.Second), + Expire: xtime.Duration(30 * time.Minute), + }, + GroupRPCClient: &RPCClient{ + RegAddrs: "127.0.0.1:2379", + Schema: "dtalk", + SrvName: "group", + Dial: xtime.Duration(time.Second), + Timeout: xtime.Duration(time.Second), + }, + PusherRPCClient: &RPCClient{ + RegAddrs: "127.0.0.1:2379", + Schema: "dtalk", + SrvName: "172.16.101.126:30003", + Dial: xtime.Duration(time.Second), + Timeout: xtime.Duration(time.Second), + }, + RevSub: &MQSubClient{ + Brokers: []string{"127.0.0.1:9092"}, + Number: 16, + MaxWorker: 1024, + }, + StoreSub: &MQSubClient{ + Brokers: []string{"127.0.0.1:9092"}, + Number: 16, + MaxWorker: 1024, + }, + } +} + +type Config struct { + AppId string + Engine string + Env string + SyncCache bool + Log xlog.Config + Trace traceConfig.Configuration + GRPCServer *grpc.ServerConfig + Reg *Reg + MySQL *MySQL + Redis *Redis + GroupRPCClient *RPCClient + PusherRPCClient *RPCClient + RevSub *MQSubClient + StoreSub *MQSubClient +} + +type MySQL struct { + Host string + Port int32 + User string + Pwd string + Db string +} + +// Reg is service register/discovery config +type Reg struct { + Schema string + SrvName string // call + RegAddrs string // etcd addrs, seperate by ',' +} + +// Redis . +type Redis struct { + Network string + Addr string + Auth string + Active int + Idle int + DialTimeout xtime.Duration + ReadTimeout xtime.Duration + WriteTimeout xtime.Duration + IdleTimeout xtime.Duration + Expire xtime.Duration +} + +// RPCClient is RPC client config. +type RPCClient struct { + RegAddrs string // etcd addrs, seperate by ',' + Schema string + SrvName string // call + Dial xtime.Duration + Timeout xtime.Duration +} + +type MQSubClient struct { + Brokers []string + Number uint32 + MaxWorker int +} diff --git a/service/record/store/config/store.toml b/service/record/store/config/store.toml new file mode 100644 index 0000000..b99f6ef --- /dev/null +++ b/service/record/store/config/store.toml @@ -0,0 +1,79 @@ +AppId = "dtalk" +Engine = "standard" +Env = "debug" +SyncCache = false + +[log] +Level = "debug" +Mode = "console" +Path = "" +Display = "json" + +[Trace] +ServiceName = "store" +Gen128Bit = true +[Trace.Sampler] +Type = "const" +Param = 1.0 +[Trace.Reporter] +LogSpans = true +LocalAgentHostPort = "127.0.0.1:6831" + +[GRPCrver] +Network= "tcp" +Addr= ":30005" +Timeout= "1s" +KeepAliveMaxConnectionIdle= "60s" +KeepAliveMaxConnectionAge= "2h" +KeepAliveMaxMaxConnectionAgeGrace= "20s" +KeepAliveTime= "60s" +KeepAliveTimeout= "20s" + + +[reg] + schema = "dtalk" + srvName = "store" + regAddrs = "127.0.0.1:2379" + +[GroupRPCClient] + regAddrs = "127.0.0.1:2379" + schema = "dtalk" + srvName = "group" + dial = "1s" + timeout = "1s" + +[MySQL] +Host= "127.0.0.1" +Port= 3306 +User= "root" +Pwd= "123456" +Db= "dtalk" + +[Redis] +network="tcp" +addr="127.0.0.1:6379" +auth="" +active=60000 +idle=1024 +dialTimeout="200ms" +readTimeout="500ms" +writeTimeout="500ms" +idleTimeout="120s" +expire="30m" + +[PusherRPCClient] + regAddrs = "127.0.0.1:2379" + schema = "dtalk" + srvName = "pusher" + dial = "1s" + timeout = "1s" + +[RevSub] +Brokers=["127.0.0.1:9092"] +Number=16 +MaxWorker=1024 + +[StoreSub] +Brokers=["127.0.0.1:9092"] +Number=16 +MaxWorker=1024 \ No newline at end of file diff --git a/service/record/store/dao/dao.go b/service/record/store/dao/dao.go new file mode 100644 index 0000000..89bd855 --- /dev/null +++ b/service/record/store/dao/dao.go @@ -0,0 +1,99 @@ +package dao + +import ( + "fmt" + "time" + + "github.com/gomodule/redigo/redis" + "github.com/rs/zerolog" + zlog "github.com/rs/zerolog/log" + "gitlab.33.cn/chat/dtalk/pkg/mysql" + "gitlab.33.cn/chat/dtalk/pkg/naming" + "gitlab.33.cn/chat/dtalk/pkg/net/grpc" + groupApi "gitlab.33.cn/chat/dtalk/service/group/api" + pusher "gitlab.33.cn/chat/dtalk/service/record/pusher/api" + "gitlab.33.cn/chat/dtalk/service/record/store/config" + "google.golang.org/grpc/resolver" +) + +type Dao struct { + appId string + log zerolog.Logger + conn *mysql.MysqlConn + redis *redis.Pool + groupRPCClient groupApi.GroupClient + pusherCli pusher.PusherClient +} + +func New(c *config.Config) *Dao { + d := &Dao{ + appId: c.AppId, + log: zlog.Logger, + conn: newDB(c.MySQL), + redis: newRedis(c.Redis), + groupRPCClient: newGroupClient(c), + pusherCli: newPushClient(c), + } + return d +} + +func newDB(cfg *config.MySQL) *mysql.MysqlConn { + c, err := mysql.NewMysqlConn(cfg.Host, fmt.Sprintf("%v", cfg.Port), + cfg.User, cfg.Pwd, cfg.Db, "UTF8MB4") + if err != nil { + panic(err) + } + return c +} + +func newRedis(c *config.Redis) *redis.Pool { + return &redis.Pool{ + MaxIdle: c.Idle, + MaxActive: c.Active, + IdleTimeout: time.Duration(c.IdleTimeout), + Dial: func() (redis.Conn, error) { + conn, err := redis.Dial(c.Network, c.Addr, + redis.DialConnectTimeout(time.Duration(c.DialTimeout)), + redis.DialReadTimeout(time.Duration(c.ReadTimeout)), + redis.DialWriteTimeout(time.Duration(c.WriteTimeout)), + redis.DialPassword(c.Auth), + ) + if err != nil { + return nil, err + } + return conn, nil + }, + } +} + +func newPushClient(cfg *config.Config) pusher.PusherClient { + rb := naming.NewResolver(cfg.PusherRPCClient.RegAddrs, cfg.PusherRPCClient.Schema) + resolver.Register(rb) + + addr := fmt.Sprintf("%s:///%s", cfg.PusherRPCClient.Schema, cfg.PusherRPCClient.SrvName) // "schema://[authority]/service" + fmt.Println("rpc client call addr:", addr) + + conn, err := grpc.NewGRPCConn(addr, time.Duration(cfg.PusherRPCClient.Dial)) + if err != nil { + panic(err) + } + return pusher.NewPusherClient(conn) +} + +func newGroupClient(cfg *config.Config) groupApi.GroupClient { + rb := naming.NewResolver(cfg.GroupRPCClient.RegAddrs, cfg.GroupRPCClient.Schema) + resolver.Register(rb) + + addr := fmt.Sprintf("%s:///%s", cfg.GroupRPCClient.Schema, cfg.GroupRPCClient.SrvName) // "schema://[authority]/service" + fmt.Println("rpc client call addr:", addr) + + conn, err := grpc.NewGRPCConn(addr, time.Duration(cfg.GroupRPCClient.Dial)) + if err != nil { + panic(err) + } + return groupApi.NewGroupClient(conn) +} + +func (d *Dao) NewTx() (*mysql.MysqlTx, error) { + return d.conn.NewTx() +} diff --git a/service/record/store/dao/dao_test.go b/service/record/store/dao/dao_test.go new file mode 100644 index 0000000..0b00c3a --- /dev/null +++ b/service/record/store/dao/dao_test.go @@ -0,0 +1,53 @@ +package dao + +import ( + "os" + "testing" + "time" + + "github.com/gomodule/redigo/redis" + "github.com/rs/zerolog" + zlog "github.com/rs/zerolog/log" + "gitlab.33.cn/chat/dtalk/pkg/mysql" + xtime "gitlab.33.cn/chat/dtalk/pkg/time" + "gitlab.33.cn/chat/dtalk/service/record/store/config" +) + +var ( + testLog zerolog.Logger + testConn *mysql.MysqlConn + testRedis *redis.Pool + + testConnRecord *mysql.MysqlConn +) + +func TestMain(m *testing.M) { + testConn = newDB(&config.MySQL{ + Host: "127.0.0.1", + Port: 3306, + User: "root", + Pwd: "123456", + Db: "dtalk", + }) + testConnRecord = newDB(&config.MySQL{ + Host: "127.0.0.1", + Port: 3306, + User: "root", + Pwd: "123456", + Db: "dtalk", + }) + testLog = zlog.Logger + testRedis = newRedis(&config.Redis{ + Network: "tcp", + Addr: "127.0.0.1:6379", + Auth: "", + Active: 60000, + Idle: 1024, + DialTimeout: xtime.Duration(200 * time.Millisecond), + ReadTimeout: xtime.Duration(500 * time.Millisecond), + WriteTimeout: xtime.Duration(500 * time.Millisecond), + IdleTimeout: xtime.Duration(120 * time.Second), + Expire: xtime.Duration(30 * time.Minute), + }) + os.Exit(m.Run()) +} diff --git a/service/record/store/dao/db.go b/service/record/store/dao/db.go new file mode 100644 index 0000000..fa15aee --- /dev/null +++ b/service/record/store/dao/db.go @@ -0,0 +1,247 @@ +package dao + +import ( + "errors" + "fmt" + "strconv" + "strings" + + "gitlab.33.cn/chat/dtalk/pkg/mysql" + "gitlab.33.cn/chat/dtalk/pkg/util" + "gitlab.33.cn/chat/dtalk/service/record/store/model" +) + +const ( + _InsertMsgContent = `INSERT INTO dtalk_msg_content(mid,seq,sender_id,receiver_id,msg_type,content,create_time,source,reference) VALUES(?,?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE mid=mid` + _InsertMsgRelation = `INSERT INTO dtalk_msg_relation(mid,owner_uid,other_uid,type,state,create_time) VALUES(?,?,?,?,?,?) ON DUPLICATE KEY UPDATE mid=mid` + _SetRecordState = `UPDATE dtalk_msg_relation SET state = ? WHERE owner_uid = ? AND mid = ?` + _DelMsgContent = `DELETE FROM dtalk_msg_content WHERE mid = ?` + + _InsertGroupMsgContent = `INSERT INTO dtalk_group_msg_content(mid,seq,sender_id,receiver_id,msg_type,content,create_time,source,reference) VALUES(?,?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE mid=mid` + _InsertGroupMsgRelationPrefix = "INSERT INTO dtalk_group_msg_relation(mid,owner_uid,other_uid,type,state,create_time) VALUES%s ON DUPLICATE KEY UPDATE mid=mid" + _SetGroupRecordState = `UPDATE dtalk_group_msg_relation SET state = ? WHERE owner_uid = ? AND mid = ?` + _DelGroupMsgContent = `DELETE FROM dtalk_group_msg_content WHERE mid = ?` + + _GetUnReceiveMsg = `SELECT co.mid as mid,co.seq as seq,co.sender_id as sender_id,co.receiver_id as receiver_id,co.msg_type as msg_type,co.content as content,co.create_time as create_time,co.source as source,co.reference as reference FROM dtalk_msg_relation AS re RIGHT JOIN dtalk_msg_content AS co ON re.mid=co.mid WHERE re.owner_uid=? AND re.type=? AND re.state=? ORDER BY re.mid` + _GetUnReceiveGroupMsg = `SELECT co.mid as mid,co.seq as seq,co.sender_id as sender_id,co.receiver_id as receiver_id,co.msg_type as msg_type,co.content as content,co.create_time as create_time,co.source as source,co.reference as reference FROM dtalk_group_msg_relation AS re RIGHT JOIN dtalk_group_msg_content AS co ON re.mid=co.mid WHERE re.owner_uid=? AND re.type=? AND re.state=? ORDER BY re.mid` + _GetUserMsg = `SELECT co.mid as mid,co.seq as seq,co.sender_id as sender_id,co.receiver_id as receiver_id,co.msg_type as msg_type,co.content as content,co.create_time as create_time,co.source as source,co.reference as reference FROM dtalk_msg_relation AS re RIGHT JOIN dtalk_msg_content AS co ON re.mid=co.mid WHERE re.owner_uid=? ORDER BY re.mid LIMIT ?,?` + _GetUserMsgAfter = `SELECT co.mid as mid,co.seq as seq,co.sender_id as sender_id,co.receiver_id as receiver_id,co.msg_type as msg_type,co.content as content,co.create_time as create_time,co.source as source,co.reference as reference FROM dtalk_msg_relation AS re RIGHT JOIN dtalk_msg_content AS co ON re.mid=co.mid WHERE re.owner_uid=? AND co.mid>? ORDER BY re.mid` + + _GetSpecifyRecords = `SELECT co.mid as mid,co.seq as seq,co.sender_id as sender_id,co.receiver_id as receiver_id,co.msg_type as msg_type,co.content as content,co.create_time as create_time,co.source as source,co.reference as reference FROM dtalk_msg_relation AS re RIGHT JOIN dtalk_msg_content AS co ON re.mid=co.mid WHERE re.mid = ?` + _GetSpecifyGroupRecords = `SELECT co.mid as mid,co.seq as seq,co.sender_id as sender_id,co.receiver_id as receiver_id,co.msg_type as msg_type,co.content as content,co.create_time as create_time,co.source as source,co.reference as reference FROM dtalk_group_msg_relation AS re RIGHT JOIN dtalk_group_msg_content AS co ON re.mid=co.mid WHERE re.mid = ?` + + _GetMsgBySeq = `SELECT mid,seq,sender_id,receiver_id,msg_type,content,create_time,source,reference FROM dtalk_msg_content WHERE sender_id=? AND seq=?` + + _IncMsgVersion = `INSERT INTO dtalk_msg_version(uid,version) VALUES(?,?) ON DUPLICATE KEY UPDATE version=version+1` + _GetMsgVersion = `SELECT version FROM dtalk_msg_version WHERE uid = ?` + + _GetPriRecords = `SELECT co.mid as mid,co.seq as seq,co.sender_id as sender_id,co.receiver_id as receiver_id,co.msg_type as msg_type,co.content as content,co.create_time as create_time FROM dtalk_msg_relation AS re RIGHT JOIN dtalk_msg_content AS co ON re.mid=co.mid WHERE re.owner_uid=? AND re.other_uid=? AND re.mid