@@ -0,0 +1,37 @@ | |||
# 多重构建,减少镜像大小 | |||
# 构建:使用golang:1.18版本 | |||
FROM registry.cn-shenzhen.aliyuncs.com/fnuoos-prd/golang:1.18.4 as build | |||
# 容器环境变量添加,会覆盖默认的变量值 | |||
ENV GO111MODULE=on | |||
ENV GOPROXY=https://goproxy.cn,direct | |||
ENV TZ="Asia/Shanghai" | |||
# 设置工作区 | |||
WORKDIR /go/release | |||
# 把全部文件添加到/go/release目录 | |||
ADD . . | |||
# 编译:把main.go编译成可执行的二进制文件,命名为zyos | |||
RUN GOOS=linux CGO_ENABLED=0 GOARCH=amd64 go build -o zyos cmd/business/main.go | |||
FROM ubuntu:xenial as prod | |||
LABEL maintainer="dengbiao" | |||
# 时区纠正 | |||
ENV TZ="Asia/Shanghai" | |||
RUN rm -f /etc/localtime \ | |||
&& ln -snf /usr/share/zoneinfo/$TZ /etc/localtime \ | |||
&& echo $TZ > /etc/timezone | |||
# 在build阶段复制可执行的go二进制文件app | |||
COPY --from=build /go/release/zyos ./zyos | |||
# 启动服务 | |||
# 优化内核参数 | |||
#RUN sysctl -w net.ipv4.tcp_fin_timeout=15 | |||
#RUN sysctl -w net.ipv4.tcp_tw_recycle=1 | |||
#RUN sysctl -w net.ipv4.ip_local_port_range=1024 65535 | |||
#RUN sysctl -p | |||
#CMD ["bash","-c","sysctl -w net.ipv4.tcp_timestamps=1 && sysctl -w net.ipv4.tcp_tw_reuse=1 && sysctl -w net.ipv4.tcp_tw_timeout=10 && sysctl -w net.ipv4.tcp_fin_timeout=10 && sysctl -w net.ipv4.ip_local_port_range='1024 65535' && ./zyos"] | |||
CMD ["./zyos"] |
@@ -0,0 +1,37 @@ | |||
# 多重构建,减少镜像大小 | |||
# 构建:使用golang:1.18版本 | |||
FROM registry.cn-shenzhen.aliyuncs.com/fnuoos-prd/golang:1.18.4 as build | |||
# 容器环境变量添加,会覆盖默认的变量值 | |||
ENV GO111MODULE=on | |||
ENV GOPROXY=https://goproxy.cn,direct | |||
ENV TZ="Asia/Shanghai" | |||
# 设置工作区 | |||
WORKDIR /go/release | |||
# 把全部文件添加到/go/release目录 | |||
ADD . . | |||
# 编译:把main.go编译成可执行的二进制文件,命名为zyos | |||
RUN GOOS=linux CGO_ENABLED=0 GOARCH=amd64 go build -o zyos cmd/connect/main.go | |||
FROM ubuntu:xenial as prod | |||
LABEL maintainer="dengbiao" | |||
# 时区纠正 | |||
ENV TZ="Asia/Shanghai" | |||
RUN rm -f /etc/localtime \ | |||
&& ln -snf /usr/share/zoneinfo/$TZ /etc/localtime \ | |||
&& echo $TZ > /etc/timezone | |||
# 在build阶段复制可执行的go二进制文件app | |||
COPY --from=build /go/release/zyos ./zyos | |||
# 启动服务 | |||
# 优化内核参数 | |||
#RUN sysctl -w net.ipv4.tcp_fin_timeout=15 | |||
#RUN sysctl -w net.ipv4.tcp_tw_recycle=1 | |||
#RUN sysctl -w net.ipv4.ip_local_port_range=1024 65535 | |||
#RUN sysctl -p | |||
#CMD ["bash","-c","sysctl -w net.ipv4.tcp_timestamps=1 && sysctl -w net.ipv4.tcp_tw_reuse=1 && sysctl -w net.ipv4.tcp_tw_timeout=10 && sysctl -w net.ipv4.tcp_fin_timeout=10 && sysctl -w net.ipv4.ip_local_port_range='1024 65535' && ./zyos"] | |||
CMD ["./zyos"] |
@@ -0,0 +1,37 @@ | |||
# 多重构建,减少镜像大小 | |||
# 构建:使用golang:1.18版本 | |||
FROM registry.cn-shenzhen.aliyuncs.com/fnuoos-prd/golang:1.18.4 as build | |||
# 容器环境变量添加,会覆盖默认的变量值 | |||
ENV GO111MODULE=on | |||
ENV GOPROXY=https://goproxy.cn,direct | |||
ENV TZ="Asia/Shanghai" | |||
# 设置工作区 | |||
WORKDIR /go/release | |||
# 把全部文件添加到/go/release目录 | |||
ADD . . | |||
# 编译:把main.go编译成可执行的二进制文件,命名为zyos | |||
RUN GOOS=linux CGO_ENABLED=0 GOARCH=amd64 go build -o zyos cmd/logic/main.go | |||
FROM ubuntu:xenial as prod | |||
LABEL maintainer="dengbiao" | |||
# 时区纠正 | |||
ENV TZ="Asia/Shanghai" | |||
RUN rm -f /etc/localtime \ | |||
&& ln -snf /usr/share/zoneinfo/$TZ /etc/localtime \ | |||
&& echo $TZ > /etc/timezone | |||
# 在build阶段复制可执行的go二进制文件app | |||
COPY --from=build /go/release/zyos ./zyos | |||
# 启动服务 | |||
# 优化内核参数 | |||
#RUN sysctl -w net.ipv4.tcp_fin_timeout=15 | |||
#RUN sysctl -w net.ipv4.tcp_tw_recycle=1 | |||
#RUN sysctl -w net.ipv4.ip_local_port_range=1024 65535 | |||
#RUN sysctl -p | |||
#CMD ["bash","-c","sysctl -w net.ipv4.tcp_timestamps=1 && sysctl -w net.ipv4.tcp_tw_reuse=1 && sysctl -w net.ipv4.tcp_tw_timeout=10 && sysctl -w net.ipv4.tcp_fin_timeout=10 && sysctl -w net.ipv4.ip_local_port_range='1024 65535' && ./zyos"] | |||
CMD ["./zyos"] |
@@ -0,0 +1,21 @@ | |||
MIT License | |||
Copyright (c) 2020 Alber | |||
Permission is hereby granted, free of charge, to any person obtaining a copy | |||
of this software and associated documentation files (the "Software"), to deal | |||
in the Software without restriction, including without limitation the rights | |||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
copies of the Software, and to permit persons to whom the Software is | |||
furnished to do so, subject to the following conditions: | |||
The above copyright notice and this permission notice shall be included in all | |||
copies or substantial portions of the Software. | |||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
SOFTWARE. |
@@ -0,0 +1,100 @@ | |||
### 简要介绍 | |||
gim是一个即时通讯服务器,代码全部使用golang完成。主要特性 | |||
1.支持tcp,websocket接入 | |||
2.离线消息同步 | |||
3.单用户多设备同时在线 | |||
4.单聊,群聊,以及房间聊天场景 | |||
5.支持服务水平扩展 | |||
6.使用领域驱动设计 | |||
gim可以作为以业务服务器的一个组件,为现有业务服务器提供im的能力,业务服务器 | |||
只需要实现business.int.proto协议中定义的GRPC接口,为gim服务提供基本的用户功能即可 | |||
### 使用技术: | |||
数据库:MySQL+Redis | |||
通讯框架:GRPC | |||
长连接通讯协议:Protocol Buffers | |||
日志框架:Zap | |||
ORM框架:GORM | |||
### 安装部署 | |||
1.首先安装MySQL,Redis | |||
2.创建数据库gim,执行sql/create_table.sql,完成初始化表的创建(数据库包含提供测试的一些初始数据) | |||
3.修改config下配置文件,使之和你本地配置一致,如果没有配置gim_env环境变量,默认会加载config/local_conf.go配置 | |||
4.分别切换到cmd的connect,logic,business目录下,执行go run main.go,启动TCP连接层服务器,WebSocket连接层服务器,逻辑层服务器,用户服务器 | |||
(注意:connect只能在linux下启动,如果想在其他平台下启动,请安装docker,执行cmd/connect/run.sh) | |||
### 项目目录简介 | |||
项目结构遵循 https://github.com/golang-standards/project-layout | |||
``` | |||
cmd: 服务启动入口 | |||
config: 服务配置 | |||
internal: 每个服务私有代码 | |||
pkg: 服务共有代码 | |||
sql: 项目sql文件 | |||
test: 长连接测试脚本 | |||
``` | |||
### 服务简介 | |||
1.connect | |||
维持与客户端的TCP和WebSocket长连接,心跳,以及TCP拆包粘包,消息编解码 | |||
2.logic | |||
设备信息,好友信息,群组信息管理,消息转发逻辑 | |||
3.business | |||
一个简单的业务服务器服务,可以根据自己的业务需求,进行扩展,但是前提是,你的业务服务器实现了business.int.proto接口 | |||
### 客户端接入流程 | |||
1.调用LogicExt.RegisterDevice接口,完成设备注册,获取设备ID(device_id),注意,一个设备只需完成一次注册即可,后续如果本地有device_id,就不需要注册了,举个例子,如果是APP第一次安装,就需要调用这个接口,后面即便是换账号登录,也不需要重新注册。 | |||
2.调用BusinessExt.SignIn接口,完成账户登录,获取账户登录的token。 | |||
3.建立长连接,使用步骤2拿到的token,完成长连接登录。 | |||
如果是web端,需要调用建立WebSocket时,如果是APP端,就需要建立TCP长连接。 | |||
在完成建立TCP长连接时,第一个包应该是长连接登录包(SignInInput),如果信息无误,客户端就会成功建立长连接。 | |||
4.使用长连接发送消息同步包(SyncInput),完成离线消息同步,注意:seq字段是客户端接收到消息的最大同步序列号,如果用户是换设备登录或者第一次登录,seq应该传0。 | |||
接下来,用户可以使用LogicExt.SendMessage接口来发送消息,消息接收方可以使用长连接接收到对应的消息。 | |||
### 网络模型 | |||
TCP的网络层使用linux的epoll实现,相比golang原生,能减少goroutine使用,从而节省系统资源占用 | |||
### 单用户多设备支持,离线消息同步 | |||
每个用户都会维护一个自增的序列号,当用户A给用户B发送消息是,首先会获取A的最大序列号,设置为这条消息的seq,持久化到用户A的消息列表, | |||
再通过长连接下发到用户A账号登录的所有设备,再获取用户B的最大序列号,设置为这条消息的seq,持久化到用户B的消息列表,再通过长连接下发 | |||
到用户B账号登录的所有设备。 | |||
假如用户的某个设备不在线,在设备长连接登录时,用本地收到消息的最大序列号,到服务器做消息同步,这样就可以保证离线消息不丢失。 | |||
### 读扩散和写扩散 | |||
首先解释一下,什么是读扩散,什么是写扩散 | |||
#### 读扩散 | |||
**简介**:群组成员发送消息时,先建立一个会话,都将这个消息写入这个会话中,同步离线消息时,需要同步这个会话的未同步消息 | |||
**优点**:每个消息只需要写入数据库一次就行,减少数据库访问次数,节省数据库空间 | |||
**缺点**:一个用户有n个群组,客户端每次同步消息时,要上传n个序列号,服务器要对这n个群组分别做消息同步 | |||
#### 写扩散 | |||
**简介**:在群组中,每个用户维持一个自己的消息列表,当群组中有人发送消息时,给群组的每个用户的消息列表插入一条消息即可 | |||
**优点**:每个用户只需要维护一个序列号和消息列表 | |||
**缺点**:一个群组有多少人,就要插入多少条消息,当群组成员很多时,DB的压力会增大 | |||
### 消息转发逻辑选型以及特点 | |||
#### 群组: | |||
采用写扩散,群组成员信息持久化到数据库保存。支持消息离线同步。 | |||
#### 房间: | |||
采用读扩散,会将消息短暂的保存到Redis,长连接登录消息同步不会同步离线消息。 | |||
### 核心流程时序图 | |||
#### 长连接登录 | |||
![登录.png](https://camo.githubusercontent.com/c3bb28e0bfe068f5ba619d571d2c665adc83138d56d1f5cb76c76c98e8d3ca74/68747470733a2f2f75706c6f61642d696d616765732e6a69616e7368752e696f2f75706c6f61645f696d616765732f353736303433392d326535346433633564643061343463312e706e673f696d6167654d6f6772322f6175746f2d6f7269656e742f7374726970253743696d61676556696577322f322f772f31323430) | |||
#### 离线消息同步 | |||
![离线消息同步.png](https://camo.githubusercontent.com/19edb5f72f832ef38ba2152f8179f91aaa55eefc3943afd44431f68824e3387b/68747470733a2f2f75706c6f61642d696d616765732e6a69616e7368752e696f2f75706c6f61645f696d616765732f353736303433392d616135313365613064653835316531322e706e673f696d6167654d6f6772322f6175746f2d6f7269656e742f7374726970253743696d61676556696577322f322f772f31323430) | |||
#### 心跳 | |||
![心跳.png](https://camo.githubusercontent.com/f8bbad45931b4b6c14d9ac6b4156459372593a8202ee7ac3978d4e54f79818aa/68747470733a2f2f75706c6f61642d696d616765732e6a69616e7368752e696f2f75706c6f61645f696d616765732f353736303433392d323664343931333734646133383433622e706e673f696d6167654d6f6772322f6175746f2d6f7269656e742f7374726970253743696d61676556696577322f322f772f31323430) | |||
#### 消息单发 | |||
c1.d1和c1.d2分别表示c1用户的两个设备d1和d2,c2.d3和c2.d4同理 | |||
![消息单发.png](https://camo.githubusercontent.com/18705cdbc15e29fdabdaf473f297337ac48d06e5e86662e9f72261d910821ce4/68747470733a2f2f75706c6f61642d696d616765732e6a69616e7368752e696f2f75706c6f61645f696d616765732f353736303433392d333566316139316338643766666661362e706e673f696d6167654d6f6772322f6175746f2d6f7269656e742f7374726970253743696d61676556696577322f322f772f31323430) | |||
#### 群组消息群发 | |||
c1,c2.c3表示一个群组中的三个用户 | |||
![消息群发.png](https://camo.githubusercontent.com/b1fdc7d86b79d9c2375c7e438f13ff6379381575d5b3873adc87ceb10d642a25/68747470733a2f2f75706c6f61642d696d616765732e6a69616e7368752e696f2f75706c6f61645f696d616765732f353736303433392d343761383763343562383939623366392e706e673f696d6167654d6f6772322f6175746f2d6f7269656e742f7374726970253743696d61676556696577322f322f772f31323430) | |||
#### APP | |||
基于Flutter写了一个简单的客户端 | |||
GitHub地址:https://github.com/alberliu/fim | |||
APP下载:https://github.com/alberliu/fim/releases/download/v1.2.0/FIM.apk | |||
APP截图: | |||
![登录.png](https://upload-images.jianshu.io/upload_images/5760439-c8c5e61815b34687.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/310) | |||
![好友.png](https://upload-images.jianshu.io/upload_images/5760439-9ea6a87711f8e749.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/310) | |||
![聊天.png](https://upload-images.jianshu.io/upload_images/5760439-2f1e7da8be247e4b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/310) | |||
![群组.png](https://upload-images.jianshu.io/upload_images/5760439-beb97223497e2ee9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/310) | |||
![我的.png](https://upload-images.jianshu.io/upload_images/5760439-aee324007a1d2eb1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/310) | |||
![消息.png](https://upload-images.jianshu.io/upload_images/5760439-47597c7c5859d515.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/310) | |||
### 联系方式 | |||
![my.png](https://upload-images.jianshu.io/upload_images/5760439-484c85f9fbda35d4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/310) | |||
### 赞赏支持 | |||
如果觉得项目对你有帮助,请支持一下 | |||
![pay.png](https://upload-images.jianshu.io/upload_images/5760439-7aac91bc83c8735f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/310) | |||
### github | |||
https://github.com/alberliu/gim |
@@ -0,0 +1,16 @@ | |||
if [[ $? -ne 0 ]]; then | |||
exit 1 | |||
fi | |||
server=$1 | |||
cd cmd/$server | |||
# 打包可执行文件 | |||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o main | |||
pwd | |||
mv main ../../docker/ | |||
cd ../../docker/ | |||
pwd | |||
# 构建镜像 | |||
docker build -t $1 . | |||
kind load docker-image $server --name kind |
@@ -0,0 +1,3 @@ | |||
cd pkg/proto | |||
protoc --go_out=plugins=grpc:../../../ *.proto | |||
cd ../../ |
@@ -0,0 +1,23 @@ | |||
# Patterns to ignore when building packages. | |||
# This supports shell glob matching, relative path matching, and | |||
# negation (prefixed with !). Only one pattern per line. | |||
.DS_Store | |||
# Common VCS dirs | |||
.git/ | |||
.gitignore | |||
.bzr/ | |||
.bzrignore | |||
.hg/ | |||
.hgignore | |||
.svn/ | |||
# Common backup files | |||
*.swp | |||
*.bak | |||
*.tmp | |||
*.orig | |||
*~ | |||
# Various IDEs | |||
.project | |||
.idea/ | |||
*.tmproj | |||
.vscode/ |
@@ -0,0 +1,24 @@ | |||
apiVersion: v2 | |||
name: gim | |||
description: A Helm chart for Kubernetes | |||
# A chart can be either an 'application' or a 'library' chart. | |||
# | |||
# Application charts are a collection of templates that can be packaged into versioned archives | |||
# to be deployed. | |||
# | |||
# Library charts provide useful utilities or functions for the chart developer. They're included as | |||
# a dependency of application charts to inject those utilities and functions into the rendering | |||
# pipeline. Library charts do not define any templates and therefore cannot be deployed. | |||
type: application | |||
# This is the chart version. This version number should be incremented each time you make changes | |||
# to the chart and its templates, including the app version. | |||
# Versions are expected to follow Semantic Versioning (https://semver.org/) | |||
version: 1.0.0 | |||
# This is the version number of the application being deployed. This version number should be | |||
# incremented each time you make changes to the application. Versions are not expected to | |||
# follow Semantic Versioning. They should reflect the version the application is using. | |||
# It is recommended to use it with quotes. | |||
appVersion: "1.16.0" |
@@ -0,0 +1,11 @@ | |||
apiVersion: v1 | |||
kind: ConfigMap | |||
metadata: | |||
name: config | |||
data: | |||
# 类属性键;每一个键都映射到一个简单的值,仅仅支持键值对,不支持嵌套 | |||
mysql: "root:ZHIoscnfnuo123@@tcp(zhios123.rwlb.rds.aliyuncs.com:3306)/gim?charset=utf8&parseTime=true" | |||
redisIP: "116.62.62.35:6379" | |||
redisPassword: "dengsanhu" | |||
pushRoomSubscribeNum: "100" | |||
pushAllSubscribeNum: "100" |
@@ -0,0 +1,11 @@ | |||
apiVersion: v1 | |||
kind: ConfigMap | |||
metadata: | |||
name: config | |||
data: | |||
# 类属性键;每一个键都映射到一个简单的值,仅仅支持键值对,不支持嵌套 | |||
mysql: "root:Fnuo123com@@tcp(119.23.182.117:3306)/gim?charset=utf8&parseTime=true" | |||
redisIP: "120.24.28.6:32572" | |||
redisPassword: "" | |||
pushRoomSubscribeNum: "100" | |||
pushAllSubscribeNum: "100" |
@@ -0,0 +1,44 @@ | |||
# 为pod中的服务赋予发现服务和读取配置的权限 | |||
apiVersion: rbac.authorization.k8s.io/v1 | |||
kind: ClusterRole | |||
metadata: | |||
name: pod-role | |||
namespace: gim | |||
rules: | |||
- apiGroups: | |||
- "" | |||
resources: | |||
- pods | |||
- pods/status | |||
- services | |||
- services/status | |||
- endpoints | |||
- endpoints/status | |||
- configmaps | |||
- configmaps/status | |||
verbs: | |||
- get | |||
- list | |||
- watch | |||
- apiGroups: | |||
- "discovery.k8s.io" | |||
resources: | |||
- endpointslices | |||
- endpointslices/status | |||
verbs: | |||
- get | |||
- list | |||
- watch | |||
--- | |||
apiVersion: rbac.authorization.k8s.io/v1 | |||
kind: ClusterRoleBinding | |||
metadata: | |||
name: argo-namespaces-binding | |||
roleRef: | |||
apiGroup: rbac.authorization.k8s.io | |||
kind: ClusterRole | |||
name: pod-role | |||
subjects: | |||
- kind: ServiceAccount | |||
name: default | |||
namespace: gim |
@@ -0,0 +1,35 @@ | |||
# 为pod中的服务赋予发现服务和读取配置的权限 | |||
apiVersion: rbac.authorization.k8s.io/v1 | |||
kind: ClusterRole | |||
metadata: | |||
name: pod-role | |||
namespace: gim | |||
rules: | |||
- apiGroups: | |||
- "" | |||
resources: | |||
- pods | |||
- pods/status | |||
- services | |||
- services/status | |||
- endpoints | |||
- endpoints/status | |||
- configmaps | |||
- configmaps/status | |||
verbs: | |||
- get | |||
- list | |||
- watch | |||
--- | |||
apiVersion: rbac.authorization.k8s.io/v1 | |||
kind: ClusterRoleBinding | |||
metadata: | |||
name: argo-namespaces-binding | |||
roleRef: | |||
apiGroup: rbac.authorization.k8s.io | |||
kind: ClusterRole | |||
name: pod-role | |||
subjects: | |||
- kind: ServiceAccount | |||
name: default | |||
namespace: gim |
@@ -0,0 +1,52 @@ | |||
# deployment 配置 | |||
apiVersion: apps/v1 | |||
kind: Deployment | |||
metadata: | |||
name: business-deployment | |||
namespace: gim | |||
labels: | |||
app: business | |||
spec: | |||
replicas: 1 | |||
selector: | |||
matchLabels: | |||
app: business | |||
template: | |||
metadata: | |||
labels: | |||
app: business | |||
spec: | |||
containers: | |||
- name: business | |||
image: 'registry.cn-shenzhen.aliyuncs.com/fnuoos-prd/zyos-gim-business:20221128-01' | |||
imagePullPolicy: Always | |||
ports: | |||
- containerPort: 8000 | |||
volumeMounts: # 映射文件为宿主机文件 | |||
- mountPath: /log/ | |||
name: log | |||
env: | |||
- name: POD_IP | |||
valueFrom: | |||
fieldRef: | |||
fieldPath: status.podIP | |||
volumes: | |||
- name: log | |||
hostPath: | |||
path: /log/ | |||
--- | |||
# service 配置 | |||
apiVersion: v1 | |||
kind: Service | |||
metadata: | |||
name: business | |||
namespace: gim | |||
labels: | |||
app: business # 只有设置label,才能被服务发现找到 | |||
spec: | |||
selector: | |||
app: business | |||
ports: | |||
- protocol: TCP | |||
port: 8000 | |||
targetPort: 8000 |
@@ -0,0 +1,63 @@ | |||
# deployment 配置 | |||
apiVersion: apps/v1 | |||
kind: Deployment | |||
metadata: | |||
name: connect-deployment | |||
namespace: gim | |||
labels: | |||
app: connect | |||
spec: | |||
replicas: 1 | |||
selector: | |||
matchLabels: | |||
app: connect | |||
template: | |||
metadata: | |||
labels: | |||
app: connect | |||
spec: | |||
containers: | |||
- name: connect | |||
image: 'registry.cn-shenzhen.aliyuncs.com/fnuoos-prd/zyos-gim-connect:20221128-01' | |||
imagePullPolicy: Always | |||
ports: | |||
- containerPort: 8000 | |||
- containerPort: 8001 | |||
- containerPort: 8002 | |||
volumeMounts: # 映射文件为宿主机文件 | |||
- mountPath: /log/ | |||
name: log | |||
env: | |||
- name: POD_IP | |||
valueFrom: | |||
fieldRef: | |||
fieldPath: status.podIP | |||
volumes: | |||
- name: log | |||
hostPath: | |||
path: /log/ | |||
--- | |||
# service 配置 | |||
apiVersion: v1 | |||
kind: Service | |||
metadata: | |||
name: connect | |||
namespace: gim | |||
labels: | |||
app: connect # 只有设置label,才能被服务发现找到 | |||
spec: | |||
selector: | |||
app: connect | |||
ports: | |||
- name: rpclisten | |||
protocol: TCP | |||
port: 8000 | |||
targetPort: 8000 | |||
- name: tcplisten | |||
protocol: TCP | |||
port: 8001 | |||
targetPort: 8001 | |||
- name: wslisten | |||
protocol: TCP | |||
port: 8002 | |||
targetPort: 8002 |
@@ -0,0 +1,52 @@ | |||
# deployment 配置 | |||
apiVersion: apps/v1 | |||
kind: Deployment | |||
metadata: | |||
name: logic-deployment | |||
namespace: gim | |||
labels: | |||
app: logic | |||
spec: | |||
replicas: 1 | |||
selector: | |||
matchLabels: | |||
app: logic | |||
template: | |||
metadata: | |||
labels: | |||
app: logic | |||
spec: | |||
containers: | |||
- name: logic | |||
image: 'registry.cn-shenzhen.aliyuncs.com/fnuoos-prd/zyos-gim-logic:20221128-01' | |||
imagePullPolicy: Always # 在kind中需要指定,不然会强制到远程拉取镜像,导致部署失败 | |||
ports: | |||
- containerPort: 8001 | |||
volumeMounts: # 映射文件为宿主机文件 | |||
- mountPath: /log/ | |||
name: log | |||
env: | |||
- name: POD_IP | |||
valueFrom: | |||
fieldRef: | |||
fieldPath: status.podIP | |||
volumes: | |||
- name: log | |||
hostPath: | |||
path: /log/ | |||
--- | |||
# service 配置 | |||
apiVersion: v1 | |||
kind: Service | |||
metadata: | |||
name: logic | |||
namespace: gim | |||
labels: | |||
app: logic # 只有设置label,才能被服务发现找到 | |||
spec: | |||
selector: | |||
app: logic | |||
ports: | |||
- protocol: TCP | |||
port: 8000 | |||
targetPort: 8000 |
@@ -0,0 +1,13 @@ | |||
server: | |||
connect: | |||
name: connect | |||
image: connect | |||
replicas: 1 | |||
logic: | |||
name: logic | |||
image: logic | |||
replicas: 1 | |||
business: | |||
name: logic | |||
image: logic | |||
replicas: 1 |
@@ -0,0 +1,53 @@ | |||
package main | |||
import ( | |||
"gim/config" | |||
"gim/internal/business/api" | |||
"gim/pkg/db" | |||
"gim/pkg/interceptor" | |||
"gim/pkg/logger" | |||
"gim/pkg/pb" | |||
"gim/pkg/urlwhitelist" | |||
"net" | |||
"net/http" | |||
_ "net/http/pprof" | |||
"os" | |||
"os/signal" | |||
"syscall" | |||
"go.uber.org/zap" | |||
"google.golang.org/grpc" | |||
) | |||
func main() { | |||
config.Init() | |||
db.Init() | |||
server := grpc.NewServer(grpc.UnaryInterceptor(interceptor.NewInterceptor("business_interceptor", urlwhitelist.Business))) | |||
// 监听服务关闭信号,服务平滑重启 | |||
go func() { | |||
c := make(chan os.Signal, 1) | |||
signal.Notify(c, syscall.SIGTERM) | |||
s := <-c | |||
logger.Logger.Info("server stop", zap.Any("signal", s)) | |||
server.GracefulStop() | |||
}() | |||
//runtime.SetBlockProfileRate(1) | |||
go func() { | |||
http.ListenAndServe(":10001", nil) | |||
}() | |||
pb.RegisterBusinessIntServer(server, &api.BusinessIntServer{}) | |||
pb.RegisterBusinessExtServer(server, &api.BusinessExtServer{}) | |||
listen, err := net.Listen("tcp", config.RPCListenAddr) | |||
if err != nil { | |||
panic(err) | |||
} | |||
logger.Logger.Info("rpc服务已经开启") | |||
err = server.Serve(listen) | |||
if err != nil { | |||
logger.Logger.Error("serve error", zap.Error(err)) | |||
} | |||
} |
@@ -0,0 +1,63 @@ | |||
package main | |||
import ( | |||
"context" | |||
"gim/config" | |||
"gim/internal/connect" | |||
"gim/pkg/db" | |||
"gim/pkg/interceptor" | |||
"gim/pkg/logger" | |||
"gim/pkg/pb" | |||
"gim/pkg/rpc" | |||
"google.golang.org/grpc" | |||
"net" | |||
"os" | |||
"os/signal" | |||
"syscall" | |||
"go.uber.org/zap" | |||
) | |||
func main() { | |||
config.Init() | |||
db.Init() | |||
// 启动TCP长链接服务器 | |||
go func() { | |||
connect.StartTCPServer(config.TCPListenAddr) | |||
}() | |||
// 启动WebSocket长链接服务器 | |||
go func() { | |||
connect.StartWSServer(config.WSListenAddr) | |||
}() | |||
// 启动服务订阅 | |||
connect.StartSubscribe() | |||
server := grpc.NewServer(grpc.UnaryInterceptor(interceptor.NewInterceptor("connect_interceptor", nil))) | |||
// 监听服务关闭信号,服务平滑重启 | |||
go func() { | |||
c := make(chan os.Signal, 1) | |||
signal.Notify(c, syscall.SIGTERM) | |||
s := <-c | |||
logger.Logger.Info("server stop start", zap.Any("signal", s)) | |||
_, _ = rpc.GetLogicIntClient().ServerStop(context.TODO(), &pb.ServerStopReq{ConnAddr: config.LocalAddr}) | |||
logger.Logger.Info("server stop end") | |||
server.GracefulStop() | |||
}() | |||
pb.RegisterConnectIntServer(server, &connect.ConnIntServer{}) | |||
listener, err := net.Listen("tcp", config.RPCListenAddr) | |||
if err != nil { | |||
panic(err) | |||
} | |||
logger.Logger.Info("rpc服务已经开启") | |||
err = server.Serve(listener) | |||
if err != nil { | |||
logger.Logger.Error("serve error", zap.Error(err)) | |||
} | |||
} |
@@ -0,0 +1,3 @@ | |||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.go | |||
echo "打包完成" | |||
docker run -v $(pwd)/:/app -p 8080:8080 -p 8081:8081 -p 50100:50100 alpine .//app/main |
@@ -0,0 +1,57 @@ | |||
package main | |||
import ( | |||
"gim/pkg/logger" | |||
"gim/pkg/util" | |||
"net/http" | |||
"strconv" | |||
"strings" | |||
"time" | |||
"go.uber.org/zap" | |||
"github.com/gin-gonic/gin" | |||
) | |||
const baseUrl = "http://111.229.238.28:8085/file/" | |||
type Response struct { | |||
Code int `json:"code"` | |||
Message string `json:"message"` | |||
Data interface{} `json:"data"` | |||
} | |||
func main() { | |||
router := gin.Default() | |||
router.Static("/file", "/root/file") | |||
// Set a lower memory limit for multipart forms (default is 32 MiB) | |||
router.MaxMultipartMemory = 8 << 20 // 8 MiB | |||
router.POST("/upload", func(c *gin.Context) { | |||
// single file | |||
file, err := c.FormFile("file") | |||
if err != nil { | |||
c.JSON(http.StatusOK, Response{Code: 1001, Message: err.Error()}) | |||
return | |||
} | |||
filenames := strings.Split(file.Filename, ".") | |||
name := strconv.FormatInt(time.Now().UnixNano(), 10) + "-" + util.RandString(30) + "." + filenames[len(filenames)-1] | |||
filePath := "/root/file/" + name | |||
err = c.SaveUploadedFile(file, filePath) | |||
if err != nil { | |||
c.JSON(http.StatusOK, Response{Code: 1001, Message: err.Error()}) | |||
return | |||
} | |||
c.JSON(http.StatusOK, Response{ | |||
Code: 0, | |||
Message: "success", | |||
Data: map[string]string{"url": baseUrl + name}, | |||
}) | |||
}) | |||
err := router.Run(":8085") | |||
if err != nil { | |||
logger.Logger.Error("Run error", zap.Error(err)) | |||
} | |||
} |
@@ -0,0 +1,54 @@ | |||
package main | |||
import ( | |||
"gim/config" | |||
"gim/internal/logic/api" | |||
"gim/internal/logic/app" | |||
"gim/internal/logic/proxy" | |||
"gim/pkg/db" | |||
"gim/pkg/interceptor" | |||
"gim/pkg/logger" | |||
"gim/pkg/pb" | |||
"gim/pkg/urlwhitelist" | |||
"net" | |||
"os" | |||
"os/signal" | |||
"syscall" | |||
"go.uber.org/zap" | |||
"google.golang.org/grpc" | |||
) | |||
func init() { | |||
proxy.MessageProxy = app.MessageApp | |||
proxy.DeviceProxy = app.DeviceApp | |||
} | |||
func main() { | |||
config.Init() | |||
db.Init() | |||
server := grpc.NewServer(grpc.UnaryInterceptor(interceptor.NewInterceptor("logic_interceptor", urlwhitelist.Logic))) | |||
// 监听服务关闭信号,服务平滑重启 | |||
go func() { | |||
c := make(chan os.Signal, 1) | |||
signal.Notify(c, syscall.SIGTERM) | |||
s := <-c | |||
logger.Logger.Info("server stop", zap.Any("signal", s)) | |||
server.GracefulStop() | |||
}() | |||
pb.RegisterLogicIntServer(server, &api.LogicIntServer{}) | |||
pb.RegisterLogicExtServer(server, &api.LogicExtServer{}) | |||
listen, err := net.Listen("tcp", config.RPCListenAddr) | |||
if err != nil { | |||
panic(err) | |||
} | |||
logger.Logger.Info("rpc服务已经开启") | |||
err = server.Serve(listen) | |||
if err != nil { | |||
logger.Logger.Error("serve error", zap.Error(err)) | |||
} | |||
} |
@@ -0,0 +1,56 @@ | |||
package config | |||
import ( | |||
"gim/pkg/logger" | |||
"go.uber.org/zap" | |||
"os" | |||
) | |||
const ( | |||
//RPCListenAddr = ":8000" | |||
//TCPListenAddr = ":8080" | |||
//WSListenAddr = ":8001" | |||
RPCListenAddr = ":8000" | |||
TCPListenAddr = ":8001" | |||
WSListenAddr = ":8002" | |||
) | |||
var ( | |||
NameSpace string = "gim" | |||
MySQL string = "root:Fnuo123com@@tcp(119.23.182.117:3306)/gim?charset=utf8&parseTime=true&loc=Local" | |||
//MySQL string = "canal:canal@tcp(zhios123.rwlb.rds.aliyuncs.com:3306)/gim?charset=utf8&parseTime=true&loc=Local" | |||
RedisIP string = "120.24.28.6:32572" | |||
RedisPassword string = "" | |||
LocalAddr string | |||
PushRoomSubscribeNum int | |||
PushAllSubscribeNum int | |||
) | |||
func Init() { | |||
//k8sClient, err := k8sutil.GetK8sClient() | |||
//if err != nil { | |||
// panic(err) | |||
//} | |||
//configmap, err := k8sClient.CoreV1().ConfigMaps(NameSpace).Get(context.TODO(), "config", metav1.GetOptions{}) | |||
//if err != nil { | |||
// panic(err) | |||
//} | |||
// | |||
//MySQL = configmap.Data["mysql"] | |||
//RedisIP = configmap.Data["redisIP"] | |||
//RedisPassword = configmap.Data["redisPassword"] | |||
//PushRoomSubscribeNum, _ = strconv.Atoi(configmap.Data["pushRoomSubscribeNum"]) | |||
//if PushRoomSubscribeNum == 0 { | |||
// panic("PushRoomSubscribeNum == 0") | |||
//} | |||
//PushAllSubscribeNum, _ = strconv.Atoi(configmap.Data["pushAllSubscribeNum"]) | |||
//if PushRoomSubscribeNum == 0 { | |||
// panic("PushAllSubscribeNum == 0") | |||
//} | |||
LocalAddr = os.Getenv("POD_IP") + RPCListenAddr | |||
logger.Level = zap.DebugLevel | |||
logger.Target = logger.Console | |||
} |
@@ -0,0 +1,3 @@ | |||
FROM scratch as final | |||
COPY main . | |||
CMD ["/main"] |
@@ -0,0 +1,2 @@ | |||
pb编译命令 | |||
protoc --go_out=plugins=grpc:../../../ *.proto |
@@ -0,0 +1,17 @@ | |||
@startuml | |||
participant client | |||
participant connect | |||
participant logic | |||
client -> connect: 心跳包请求 | |||
connect --> client: 心跳包响应 | |||
client -> client: 等待一段时间 | |||
client -> connect: 心跳包请求 | |||
connect --> client: 心跳包响应 | |||
connect -> connect: 连续n次没有收到,释放连接 | |||
connect -> logic: 通知设备下线 | |||
@enduml |
@@ -0,0 +1,31 @@ | |||
@startuml | |||
participant c1.d1 | |||
participant c1.d2 | |||
participant c2.d3 | |||
participant c2.d4 | |||
participant connect | |||
participant logic | |||
c1.d1 -> logic: c1给c2用户发送消息 | |||
logic --> c1.d1 : 返回消息发送成功 | |||
logic -> logic: 获取c1用户下一个消息序列号 | |||
logic -> logic: 将消息持久化到c1用户的消息列表 | |||
logic -> logic: 查询c1用户其他在线设备 | |||
logic --> connect: 给设备d2发送消息 | |||
connect --> c1.d2: 给设备d2发送消息 | |||
c1.d2 ->connect : 消息ack | |||
connect -> logic: 消息ack | |||
logic -> logic: 获取c2用户下一个消息序列号 | |||
logic -> logic: 将消息持久化到c2用户的消息列表 | |||
logic -> logic: 查询c2用户所有在线设备 | |||
logic -> connect: 给设备d3发送消息 | |||
connect -> c2.d3: 给设备d3发送消息 | |||
c2.d3 ->connect : 消息ack | |||
connect -> logic: 消息ack | |||
logic -> connect: 给设备d4发送消息 | |||
connect -> c2.d4: 给设备d4发送消息 | |||
c2.d4 ->connect : 消息ack | |||
connect -> logic: 消息ack | |||
@enduml |
@@ -0,0 +1,31 @@ | |||
@startuml | |||
participant c1 | |||
participant c2 | |||
participant c3 | |||
participant connect | |||
participant logic | |||
c1 -> logic: 发送消息到群组 | |||
logic --> c1: 消息发送成功 | |||
logic -> logic: 查询群组所有成员 | |||
logic -> logic: 将消息持久化到c1的消息列表 | |||
logic -> connect: 发送消息给c1的其他在线设备 | |||
connect -> c1: 发送消息给c1的其他在线设备 | |||
c1 -> connect: 消息ack | |||
connect -> logic: 消息ack | |||
logic -> logic: 将消息持久化到c2的消息列表 | |||
logic -> connect: 发送消息给c2的其他在线设备 | |||
connect -> c2: 发送消息给c2的其他在线设备 | |||
c2 -> connect: 消息ack | |||
connect -> logic: 消息ack | |||
logic -> logic: 将消息持久化到c3的消息列表 | |||
logic -> connect: 发送消息给c3的其他在线设备 | |||
connect -> c3: 发送消息给c3的其他在线设备 | |||
c3 -> connect: 消息ack | |||
connect -> logic: 消息ack | |||
@enduml |
@@ -0,0 +1,15 @@ | |||
@startuml | |||
participant client | |||
participant connect | |||
participant logic | |||
client -> connect: 设备鉴权 | |||
connect -> logic: 设备鉴权 | |||
logic -> logic: 检查token是否合法 | |||
logic --> connect: 返回校验结果 | |||
connect --> client: 返回校验结果 | |||
connect -> connect: 如果鉴权失败,断开连接 | |||
@enduml |
@@ -0,0 +1,16 @@ | |||
@startuml | |||
participant client | |||
participant connect | |||
participant logic | |||
client -> connect: 离线消息同步 | |||
connect -> logic: 离线消息同步 | |||
logic -> logic: 如果seq!=0,同步序列号大于seq的消息,否则,同步用户未收到的消息 | |||
logic --> connect: 返回离线消息 | |||
connect --> client: 返回离线消息 | |||
client -> connect: 消息ack | |||
connect -> logic: 消息ack | |||
@enduml |
@@ -0,0 +1,138 @@ | |||
### 错误处理,链路追踪,日志打印 | |||
系统中的错误一般可以归类为两种,一种是业务定义的错误,一种就是未知的错误,在业务正式上线的时候,业务定义的错误的属于正常业务逻辑,不需要打印出来, | |||
但是未知的错误,我们就需要打印出来,我们不仅要知道是什么错误,还要知道错误的调用堆栈,所以这里我对GRPC的错误进行了一些封装,使之包含调用堆栈。 | |||
```go | |||
func WrapError(err error) error { | |||
if err == nil { | |||
return nil | |||
} | |||
s := &spb.Status{ | |||
Code: int32(codes.Unknown), | |||
Message: err.Error(), | |||
Details: []*any.Any{ | |||
{ | |||
TypeUrl: TypeUrlStack, | |||
Value: util.Str2bytes(stack()), | |||
}, | |||
}, | |||
} | |||
return status.FromProto(s).Err() | |||
} | |||
// Stack 获取堆栈信息 | |||
func stack() string { | |||
var pc = make([]uintptr, 20) | |||
n := runtime.Callers(3, pc) | |||
var build strings.Builder | |||
for i := 0; i < n; i++ { | |||
f := runtime.FuncForPC(pc[i] - 1) | |||
file, line := f.FileLine(pc[i] - 1) | |||
n := strings.Index(file, name) | |||
if n != -1 { | |||
s := fmt.Sprintf(" %s:%d \n", file[n:], line) | |||
build.WriteString(s) | |||
} | |||
} | |||
return build.String() | |||
} | |||
``` | |||
这样,不仅可以拿到错误的堆栈,错误的堆栈也可以跨RPC传输,但是,但是这样你只能拿到当前服务的堆栈,却不能拿到调用方的堆栈,就比如说,A服务调用 | |||
B服务,当B服务发生错误时,在A服务通过日志打印错误的时候,我们只打印了B服务的调用堆栈,怎样可以把A服务的堆栈打印出来。我们在A服务调用的地方也获取 | |||
一次堆栈。 | |||
```go | |||
func WrapRPCError(err error) error { | |||
if err == nil { | |||
return nil | |||
} | |||
e, _ := status.FromError(err) | |||
s := &spb.Status{ | |||
Code: int32(e.Code()), | |||
Message: e.Message(), | |||
Details: []*any.Any{ | |||
{ | |||
TypeUrl: TypeUrlStack, | |||
Value: util.Str2bytes(GetErrorStack(e) + " --grpc-- \n" + stack()), | |||
}, | |||
}, | |||
} | |||
return status.FromProto(s).Err() | |||
} | |||
func interceptor(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 gerrors.WrapRPCError(err) | |||
} | |||
var LogicIntClient pb.LogicIntClient | |||
func InitLogicIntClient(addr string) { | |||
conn, err := grpc.DialContext(context.TODO(), addr, grpc.WithInsecure(), grpc.WithUnaryInterceptor(interceptor)) | |||
if err != nil { | |||
logger.Sugar.Error(err) | |||
panic(err) | |||
} | |||
LogicIntClient = pb.NewLogicIntClient(conn) | |||
} | |||
``` | |||
像这样,就可以获取完整一次调用堆栈。 | |||
错误打印也没有必要在函数返回错误的时候,每次都去打印。因为错误已经包含了堆栈信息 | |||
```go | |||
// 错误的方式 | |||
if err != nil { | |||
logger.Sugar.Error(err) | |||
return err | |||
} | |||
// 正确的方式 | |||
if err != nil { | |||
return err | |||
} | |||
``` | |||
然后,我们在上层统一打印就可以 | |||
```go | |||
func startServer { | |||
extListen, err := net.Listen("tcp", conf.LogicConf.ClientRPCExtListenAddr) | |||
if err != nil { | |||
panic(err) | |||
} | |||
extServer := grpc.NewServer(grpc.UnaryInterceptor(LogicClientExtInterceptor)) | |||
pb.RegisterLogicClientExtServer(extServer, &LogicClientExtServer{}) | |||
err = extServer.Serve(extListen) | |||
if err != nil { | |||
panic(err) | |||
} | |||
} | |||
func LogicClientExtInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { | |||
defer logPanic("logic_client_ext_interceptor", ctx, req, info, &err) | |||
resp, err = handler(ctx, req) | |||
logger.Logger.Debug("logic_client_ext_interceptor", zap.Any("info", info), zap.Any("ctx", ctx), zap.Any("req", req), | |||
zap.Any("resp", resp), zap.Error(err)) | |||
s, _ := status.FromError(err) | |||
if s.Code() != 0 && s.Code() < 1000 { | |||
md, _ := metadata.FromIncomingContext(ctx) | |||
logger.Logger.Error("logic_client_ext_interceptor", zap.String("method", info.FullMethod), zap.Any("md", md), zap.Any("req", req), | |||
zap.Any("resp", resp), zap.Error(err), zap.String("stack", gerrors.GetErrorStack(s))) | |||
} | |||
return | |||
} | |||
``` | |||
这样做的前提就是,在业务代码中透传context,golang不像其他语言,可以在线程本地保存变量,像Java的ThreadLocal,所以只能通过函数参数的形式进行传递,im中,service层函数的第一个参数 | |||
都是context,但是dao层和cache层就不需要了,不然,显得代码臃肿。 | |||
最后可以在客户端的每次请求添加一个随机的request_id,这样客户端到服务的每次请求都可以串起来了。 | |||
```go | |||
func getCtx() context.Context { | |||
token, _ := util.GetToken(1, 2, 3, time.Now().Add(1*time.Hour).Unix(), util.PublicKey) | |||
return metadata.NewOutgoingContext(context.TODO(), metadata.Pairs( | |||
"app_id", "1", | |||
"user_id", "2", | |||
"device_id", "3", | |||
"token", token, | |||
"request_id", strconv.FormatInt(time.Now().UnixNano(), 10))) | |||
} | |||
``` |
@@ -0,0 +1,81 @@ | |||
module gim | |||
go 1.18 | |||
require ( | |||
code.fnuoos.com/go_rely_warehouse/zyos_go_jg_push.git v1.0.5 | |||
github.com/alberliu/gn v1.10.0 | |||
github.com/gin-gonic/gin v1.7.7 | |||
github.com/go-redis/redis v6.15.9+incompatible | |||
github.com/go-sql-driver/mysql v1.6.0 | |||
github.com/golang/protobuf v1.5.2 | |||
github.com/gorilla/websocket v1.4.2 | |||
github.com/jinzhu/gorm v1.9.16 | |||
github.com/json-iterator/go v1.1.12 | |||
go.uber.org/zap v1.21.0 | |||
google.golang.org/genproto v0.0.0-20221018160656-63c7b68cfc55 | |||
google.golang.org/grpc v1.50.1 | |||
google.golang.org/protobuf v1.28.1 | |||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 | |||
k8s.io/api v0.25.1 | |||
k8s.io/apimachinery v0.25.1 | |||
k8s.io/client-go v0.25.1 | |||
) | |||
require ( | |||
github.com/PuerkitoBio/purell v1.1.1 // indirect | |||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect | |||
github.com/davecgh/go-spew v1.1.1 // indirect | |||
github.com/emicklei/go-restful/v3 v3.8.0 // indirect | |||
github.com/gin-contrib/sse v0.1.0 // indirect | |||
github.com/go-logr/logr v1.2.3 // indirect | |||
github.com/go-openapi/jsonpointer v0.19.5 // indirect | |||
github.com/go-openapi/jsonreference v0.19.5 // indirect | |||
github.com/go-openapi/swag v0.19.14 // indirect | |||
github.com/go-playground/locales v0.13.0 // indirect | |||
github.com/go-playground/universal-translator v0.17.0 // indirect | |||
github.com/go-playground/validator/v10 v10.4.1 // indirect | |||
github.com/gogo/protobuf v1.3.2 // indirect | |||
github.com/google/gnostic v0.5.7-v3refs // indirect | |||
github.com/google/gofuzz v1.1.0 // indirect | |||
github.com/gookit/color v1.3.6 // indirect | |||
github.com/jinzhu/inflection v1.0.0 // indirect | |||
github.com/josharian/intern v1.0.0 // indirect | |||
github.com/leodido/go-urn v1.2.0 // indirect | |||
github.com/mailru/easyjson v0.7.6 // indirect | |||
github.com/mattn/go-isatty v0.0.12 // indirect | |||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect | |||
github.com/modern-go/reflect2 v1.0.2 // indirect | |||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect | |||
github.com/syyongx/php2go v0.9.7 // indirect | |||
github.com/tidwall/gjson v1.14.1 // indirect | |||
github.com/tidwall/match v1.1.1 // indirect | |||
github.com/tidwall/pretty v1.2.0 // indirect | |||
github.com/ugorji/go/codec v1.1.7 // indirect | |||
go.uber.org/atomic v1.7.0 // indirect | |||
go.uber.org/multierr v1.6.0 // indirect | |||
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect | |||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect | |||
golang.org/x/sys v0.1.0 // indirect | |||
golang.org/x/term v0.1.0 // indirect | |||
golang.org/x/text v0.4.0 // indirect | |||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect | |||
google.golang.org/appengine v1.6.7 // indirect | |||
gopkg.in/inf.v0 v0.9.1 // indirect | |||
gopkg.in/yaml.v2 v2.4.0 // indirect | |||
gopkg.in/yaml.v3 v3.0.1 // indirect | |||
k8s.io/klog/v2 v2.70.1 // indirect | |||
k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect | |||
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect | |||
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect | |||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect | |||
sigs.k8s.io/yaml v1.2.0 // indirect | |||
) | |||
require ( | |||
github.com/google/go-cmp v0.5.9 // indirect | |||
github.com/onsi/ginkgo v1.16.5 // indirect | |||
github.com/qiniu/api.v7/v7 v7.8.2 | |||
golang.org/x/net v0.1.0 // indirect | |||
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect | |||
) |
@@ -0,0 +1,347 @@ | |||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= | |||
code.fnuoos.com/go_rely_warehouse/zyos_go_jg_push.git v1.0.5 h1:+MIZPOEhxTiu79CHwz5LuK5tSv40XxAHwftOUlyr+sY= | |||
code.fnuoos.com/go_rely_warehouse/zyos_go_jg_push.git v1.0.5/go.mod h1:IEw6A61Kp2ctoeKIQMFVM/cFq9PUDAzJMb5lVXiEY2Y= | |||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= | |||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= | |||
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= | |||
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-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/alberliu/gn v1.10.0 h1:BQcfgp1c8wc+AXvutImeJ5RAr7Lh/tc23vELJ3dgh10= | |||
github.com/alberliu/gn v1.10.0/go.mod h1:Rm7O35H784ZsqxJr/3VcNgyHHfI7jf7GFTQ+o/fXAsg= | |||
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= | |||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= | |||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= | |||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= | |||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= | |||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= | |||
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/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM= | |||
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= | |||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= | |||
github.com/emicklei/go-restful/v3 v3.8.0 h1:eCZ8ulSerjdAiaNpF7GxXIE7ZCMo1moN1qX+S609eVw= | |||
github.com/emicklei/go-restful/v3 v3.8.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= | |||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= | |||
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/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/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 v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs= | |||
github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U= | |||
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= | |||
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= | |||
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= | |||
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= | |||
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.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM= | |||
github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= | |||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= | |||
github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= | |||
github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= | |||
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.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= | |||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= | |||
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= | |||
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= | |||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= | |||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= | |||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= | |||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= | |||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= | |||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= | |||
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/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= | |||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= | |||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= | |||
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.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.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/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= | |||
github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= | |||
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.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | |||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= | |||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= | |||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= | |||
github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= | |||
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= | |||
github.com/gookit/color v1.3.6 h1:Rgbazd4JO5AgSTVGS3o0nvaSdwdrS8bzvIXwtK6OiMk= | |||
github.com/gookit/color v1.3.6/go.mod h1:R3ogXq2B9rTbXoSHJ1HyUVAZ3poOJHpd9nQmyGZsfvQ= | |||
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/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= | |||
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/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/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= | |||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= | |||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= | |||
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/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= | |||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= | |||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= | |||
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/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.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4= | |||
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= | |||
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.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= | |||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= | |||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= | |||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= | |||
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/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-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= | |||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= | |||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= | |||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= | |||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= | |||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= | |||
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/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= | |||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= | |||
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/ginkgo/v2 v2.1.6 h1:Fx2POJZfKRQcM1pH49qSZiYeu319wji004qX+GDovrU= | |||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= | |||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= | |||
github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q= | |||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | |||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= | |||
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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= | |||
github.com/qiniu/api.v7/v7 v7.8.2 h1:f08kI0MmsJNzK4sUS8bG3HDH67ktwd/ji23Gkiy2ra4= | |||
github.com/qiniu/api.v7/v7 v7.8.2/go.mod h1:FPsIqxh1Ym3X01sANE5ZwXfLZSWoCUp5+jNI8cLo3l0= | |||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= | |||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= | |||
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= | |||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | |||
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/syyongx/php2go v0.9.7 h1:boZtLbm2xYbW49mX9M7Vq2zkVhBhv3fCqs2T16d2bGA= | |||
github.com/syyongx/php2go v0.9.7/go.mod h1:meN2eIhhUoxOd2nMxbpe8g6cFPXI5O9/UAAuz7oDdzw= | |||
github.com/tidwall/gjson v1.14.1 h1:iymTbGkQBhveq21bEvAQ81I0LEBork8BFe1CUZXdyuo= | |||
github.com/tidwall/gjson v1.14.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= | |||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= | |||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= | |||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= | |||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= | |||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= | |||
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= | |||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= | |||
github.com/yuin/goldmark v1.1.27/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= | |||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= | |||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= | |||
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= | |||
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= | |||
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= | |||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= | |||
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= | |||
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= | |||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | |||
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | |||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | |||
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd h1:XcWmESyNjXJMLahc3mqVQJcgSTDxFxhETVlfk9uGc38= | |||
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= | |||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= | |||
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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= | |||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= | |||
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/net v0.0.0-20180218175443-cbe0f9307d01/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-20190213061140-3a22650c66bd/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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | |||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= | |||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/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-20200202094626-16171245cfb2/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-20200324143707-d3edc9973b7e/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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= | |||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= | |||
golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= | |||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= | |||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= | |||
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 h1:nt+Q6cXKz4MosCSpnbMtqiQ8Oz0pxTef2B4Vca2lvfk= | |||
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= | |||
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-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-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-20180830151530-49385e6e1522/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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | |||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/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-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | |||
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= | |||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | |||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | |||
golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= | |||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= | |||
golang.org/x/text v0.3.0/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.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= | |||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= | |||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= | |||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | |||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/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-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-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= | |||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | |||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= | |||
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.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= | |||
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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | |||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= | |||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= | |||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= | |||
google.golang.org/appengine v1.6.7/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-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= | |||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= | |||
google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= | |||
google.golang.org/genproto v0.0.0-20221018160656-63c7b68cfc55 h1:U1u4KB2kx6KR/aJDjQ97hZ15wQs8ZPvDcGcRynBhkvg= | |||
google.golang.org/genproto v0.0.0-20221018160656-63c7b68cfc55/go.mod h1:45EK0dUbEZ2NHjCeAd2LXmyjAgGUGrpGROgjhC3ADck= | |||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= | |||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= | |||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= | |||
google.golang.org/grpc v1.50.1 h1:DS/BukOZWp8s6p4Dt/tOaJaTQyPyOoCcrjroHuCeLzY= | |||
google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= | |||
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.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.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= | |||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= | |||
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 h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= | |||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | |||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= | |||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= | |||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= | |||
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/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/yaml.v2 v2.2.2/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.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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | |||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | |||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | |||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= | |||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= | |||
k8s.io/api v0.25.1 h1:yL7du50yc93k17nH/Xe9jujAYrcDkI/i5DL1jPz4E3M= | |||
k8s.io/api v0.25.1/go.mod h1:hh4itDvrWSJsmeUc28rIFNri8MatNAAxJjKcQmhX6TU= | |||
k8s.io/apimachinery v0.25.1 h1:t0XrnmCEHVgJlR2arwO8Awp9ylluDic706WePaYCBTI= | |||
k8s.io/apimachinery v0.25.1/go.mod h1:hqqA1X0bsgsxI6dXsJ4HnNTBOmJNxyPp8dw3u2fSHwA= | |||
k8s.io/client-go v0.25.1 h1:uFj4AJKtE1/ckcSKz8IhgAuZTdRXZDKev8g387ndD58= | |||
k8s.io/client-go v0.25.1/go.mod h1:rdFWTLV/uj2C74zGbQzOsmXPUtMAjSf7ajil4iJUNKo= | |||
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= | |||
k8s.io/klog/v2 v2.70.1 h1:7aaoSdahviPmR+XkS7FyxlkkXs6tHISSG03RxleQAVQ= | |||
k8s.io/klog/v2 v2.70.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= | |||
k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 h1:MQ8BAZPZlWk3S9K4a9NCkIFQtZShWqoha7snGixVgEA= | |||
k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1/go.mod h1:C/N6wCaBHeBHkHUesQOQy2/MZqGgMAFPqGsGQLdbZBU= | |||
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed h1:jAne/RjBTyawwAy0utX5eqigAwz/lQhTmy+Hr/Cpue4= | |||
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= | |||
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= | |||
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= | |||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= | |||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= | |||
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= | |||
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= |
@@ -0,0 +1,177 @@ | |||
package api | |||
import ( | |||
"context" | |||
"encoding/json" | |||
"errors" | |||
"gim/internal/business/app" | |||
comm "gim/internal/business/comm" | |||
"gim/internal/business/comm/utils" | |||
"gim/internal/business/domain/user/repo" | |||
friend2 "gim/internal/logic/domain/friend" | |||
"gim/internal/logic/domain/group/model" | |||
repo2 "gim/internal/logic/domain/group/repo" | |||
"gim/internal/logic/domain/message/service" | |||
"gim/pkg/grpclib" | |||
"gim/pkg/pb" | |||
"strconv" | |||
"time" | |||
) | |||
type BusinessExtServer struct{} | |||
func (s *BusinessExtServer) ComplainGroup(ctx context.Context, req *pb.ComplainGroupReq) (*pb.Empty, error) { | |||
now := time.Now() | |||
userId, _, err := grpclib.GetCtxData(ctx) | |||
if err != nil { | |||
return nil, err | |||
} | |||
imageList, _ := json.Marshal(req.ImageList) | |||
groupComplain := model.GroupComplain{ | |||
GroupId: req.GroupId, | |||
UserId: userId, | |||
ComplainType: int(req.ComplainType), | |||
Text: req.Text, | |||
ImageList: string(imageList), | |||
Status: 0, | |||
CreateTime: now, | |||
UpdateTime: now, | |||
} | |||
return new(pb.Empty), repo2.GroupComplainDao.Save(&groupComplain) | |||
} | |||
func (s *BusinessExtServer) IsFriends(ctx context.Context, req *pb.IsFriendsReq) (*pb.IsFriendsResp, error) { | |||
masterId, err := grpclib.GetCtxMasterId(ctx) | |||
if err != nil { | |||
return nil, err | |||
} | |||
user, err := repo.UserRepo.GetByPhoneNumber(req.UserPhone, utils.StrToInt64(masterId)) | |||
if err != nil { | |||
return nil, err | |||
} | |||
if user == nil { | |||
return nil, errors.New("未查询到自身用户信息") | |||
} | |||
userFriend, err := repo.UserRepo.GetByPhoneNumber(req.FriendPhone, utils.StrToInt64(masterId)) | |||
if err != nil { | |||
return nil, err | |||
} | |||
if userFriend == nil { | |||
return nil, errors.New("未查询到好友用户信息") | |||
} | |||
friend, err := friend2.FriendRepo.Get(user.Id, userFriend.Id) | |||
if err != nil { | |||
return nil, err | |||
} | |||
var isFriend = int64(1) | |||
if friend == nil || friend.Status == friend2.FriendStatusApply { | |||
isFriend = 2 | |||
} | |||
return &pb.IsFriendsResp{ | |||
User: &pb.User{ | |||
UserId: userFriend.Id, | |||
Nickname: userFriend.Nickname, | |||
Sex: userFriend.Sex, | |||
AvatarUrl: userFriend.AvatarUrl, | |||
MasterId: userFriend.MasterId, | |||
IsAutoAddedFriends: int64(userFriend.IsAutoAddedFriends), | |||
}, | |||
IsFriend: isFriend, | |||
}, err | |||
} | |||
func (s *BusinessExtServer) EmoticonList(ctx context.Context, empty *pb.Empty) (*pb.EmoticonListResp, error) { | |||
masterId, err := grpclib.GetCtxMasterId(ctx) | |||
if err != nil { | |||
return nil, err | |||
} | |||
list, err := service.EmoticonList(masterId) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return &pb.EmoticonListResp{Emoticons: list}, err | |||
} | |||
func (s *BusinessExtServer) SignIn(ctx context.Context, req *pb.SignInReq) (*pb.SignInResp, error) { | |||
utils.FilePutContents("sign_in", utils.SerializeStr(map[string]interface{}{ | |||
"args": req, | |||
})) | |||
isNew, userId, token, masterId, err := app.AuthApp.SignIn(ctx, req.PhoneNumber, req.Code, req.MasterId, req.DeviceId, req.PushAlia, req.Nickname, req.AvatarUrl) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return &pb.SignInResp{ | |||
IsNew: isNew, | |||
UserId: userId, | |||
Token: token, | |||
MasterId: masterId, | |||
}, nil | |||
} | |||
func (s *BusinessExtServer) GetUser(ctx context.Context, req *pb.GetUserReq) (*pb.GetUserResp, error) { | |||
var user *pb.User | |||
var err error | |||
if req.Phone != "" { | |||
masterId, err := grpclib.GetCtxMasterId(ctx) | |||
if err != nil { | |||
return nil, err | |||
} | |||
useInfo, err := repo.UserRepo.GetByPhoneNumber(req.Phone, utils.StrToInt64(masterId)) | |||
user = &pb.User{ | |||
UserId: useInfo.Id, | |||
Nickname: useInfo.Nickname, | |||
Sex: useInfo.Sex, | |||
AvatarUrl: useInfo.AvatarUrl, | |||
MasterId: useInfo.MasterId, | |||
IsAutoAddedFriends: int64(useInfo.IsAutoAddedFriends), | |||
PhoneNumber: useInfo.PhoneNumber, | |||
} | |||
} else { | |||
userId, _, err := grpclib.GetCtxData(ctx) | |||
if err != nil { | |||
return nil, err | |||
} | |||
if req.UserId != 0 { | |||
userId = req.UserId | |||
} | |||
user, err = app.UserApp.Get(ctx, userId) | |||
} | |||
return &pb.GetUserResp{User: user}, err | |||
} | |||
func (s *BusinessExtServer) UpdateUser(ctx context.Context, req *pb.UpdateUserReq) (*pb.Empty, error) { | |||
userId, _, err := grpclib.GetCtxData(ctx) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return new(pb.Empty), app.UserApp.Update(ctx, userId, req) | |||
} | |||
func (s *BusinessExtServer) SearchUser(ctx context.Context, req *pb.SearchUserReq) (*pb.SearchUserResp, error) { | |||
users, err := app.UserApp.Search(ctx, req.Key, req.MasterId) | |||
return &pb.SearchUserResp{Users: users}, err | |||
} | |||
func (s *BusinessExtServer) CloudUploadFile(ctx context.Context, req *pb.CloudUploadFileReq) (*pb.CloudUploadFileResp, error) { | |||
userId, _, err := grpclib.GetCtxData(ctx) | |||
if err != nil { | |||
return nil, err | |||
} | |||
upload, err := comm.CloudUpload.FileReqUpload(ctx, strconv.FormatInt(userId, 10), req.Dir, req.FileName, utils.StrToInt64(req.FileSize)) | |||
data := utils.SerializeStr(upload) | |||
var result map[string]string | |||
utils.Unserialize([]byte(data), &result) | |||
return &pb.CloudUploadFileResp{ | |||
Method: result["method"], | |||
Host: result["host"], | |||
Key: result["key"], | |||
Token: result["token"], | |||
}, err | |||
} |
@@ -0,0 +1,59 @@ | |||
package api | |||
import ( | |||
"context" | |||
"fmt" | |||
"gim/pkg/pb" | |||
"strconv" | |||
"testing" | |||
"time" | |||
"google.golang.org/grpc" | |||
"google.golang.org/grpc/metadata" | |||
) | |||
func getBusinessExtClient() pb.BusinessExtClient { | |||
conn, err := grpc.Dial("im-rpc-business.izhyin.com:8000", grpc.WithInsecure()) | |||
if err != nil { | |||
fmt.Println(err) | |||
return nil | |||
} | |||
return pb.NewBusinessExtClient(conn) | |||
} | |||
func getCtx() context.Context { | |||
token := "0" | |||
return metadata.NewOutgoingContext(context.TODO(), metadata.Pairs( | |||
"user_id", "1", | |||
"device_id", "1", | |||
"token", token, | |||
"request_id", strconv.FormatInt(time.Now().UnixNano(), 10))) | |||
} | |||
func TestUserExtServer_SignIn(t *testing.T) { | |||
resp, err := getBusinessExtClient().SignIn(getCtx(), &pb.SignInReq{ | |||
PhoneNumber: "18229775311", | |||
DeviceId: 1, | |||
MasterId: 123456, | |||
}) | |||
if err != nil { | |||
fmt.Println(err) | |||
} | |||
fmt.Printf("%+v\n", resp) | |||
} | |||
func TestUserExtServer_GetUser(t *testing.T) { | |||
resp, err := getBusinessExtClient().GetUser(getCtx(), &pb.GetUserReq{UserId: 2}) | |||
if err != nil { | |||
fmt.Println(err) | |||
} | |||
fmt.Printf("%+v\n", resp) | |||
} | |||
func TestUserExtServer_SearchUser(t *testing.T) { | |||
resp, err := getBusinessExtClient().SearchUser(getCtx(), &pb.SearchUserReq{Key: "18229"}) | |||
if err != nil { | |||
fmt.Println(err) | |||
} | |||
fmt.Printf("%+v\n", resp) | |||
} |
@@ -0,0 +1,33 @@ | |||
package api | |||
import ( | |||
"context" | |||
"gim/internal/business/app" | |||
"gim/pkg/pb" | |||
) | |||
type BusinessIntServer struct{} | |||
func (s *BusinessIntServer) MasterAuth(ctx context.Context, req *pb.MasterAuthReq) (*pb.Empty, error) { | |||
//TODO implement me | |||
return &pb.Empty{}, app.AuthApp.AuthMaster(ctx, req.MasterId) | |||
} | |||
func (*BusinessIntServer) Auth(ctx context.Context, req *pb.AuthReq) (*pb.Empty, error) { | |||
return &pb.Empty{}, app.AuthApp.Auth(ctx, req.UserId, req.DeviceId, req.Token) | |||
} | |||
func (*BusinessIntServer) GetUser(ctx context.Context, req *pb.GetUserReq) (*pb.GetUserResp, error) { | |||
user, err := app.UserApp.Get(ctx, req.UserId) | |||
return &pb.GetUserResp{User: user}, err | |||
} | |||
func (*BusinessIntServer) GetUsers(ctx context.Context, req *pb.GetUsersReq) (*pb.GetUsersResp, error) { | |||
var userIds = make([]int64, 0, len(req.UserIds)) | |||
for k := range req.UserIds { | |||
userIds = append(userIds, k) | |||
} | |||
users, err := app.UserApp.GetByIds(ctx, userIds) | |||
return &pb.GetUsersResp{Users: users}, err | |||
} |
@@ -0,0 +1,42 @@ | |||
package api | |||
import ( | |||
"fmt" | |||
"gim/pkg/pb" | |||
"testing" | |||
"google.golang.org/grpc" | |||
) | |||
func getBusinessIntClient() pb.BusinessIntClient { | |||
//conn, err := grpc.Dial("localhost:50300", grpc.WithInsecure()) | |||
conn, err := grpc.Dial("im-rpc-business.izhyin.com:8000", grpc.WithInsecure()) | |||
if err != nil { | |||
fmt.Println(err) | |||
return nil | |||
} | |||
return pb.NewBusinessIntClient(conn) | |||
} | |||
func TestUserIntServer_Auth(t *testing.T) { | |||
_, err := getBusinessIntClient().Auth(getCtx(), &pb.AuthReq{ | |||
UserId: 2, | |||
DeviceId: 1, | |||
Token: "0", | |||
}) | |||
fmt.Println(err) | |||
} | |||
func TestUserIntServer_GetUsers(t *testing.T) { | |||
resp, err := getBusinessIntClient().GetUsers(getCtx(), &pb.GetUsersReq{ | |||
UserIds: map[int64]int32{1: 0, 2: 0, 3: 0}, | |||
}) | |||
if err != nil { | |||
fmt.Println(err) | |||
return | |||
} | |||
for k, v := range resp.Users { | |||
fmt.Printf("%+-5v %+v\n", k, v) | |||
} | |||
} |
@@ -0,0 +1,25 @@ | |||
package app | |||
import ( | |||
"context" | |||
"gim/internal/business/domain/user/service" | |||
) | |||
type authApp struct{} | |||
var AuthApp = new(authApp) | |||
// SignIn 长连接登录 | |||
func (*authApp) SignIn(ctx context.Context, phoneNumber, code string, masterId, deviceId int64, pushAlia, nickname, avatarUrl string) (bool, int64, string, int64, error) { | |||
return service.AuthService.SignIn(ctx, phoneNumber, code, masterId, deviceId, pushAlia, nickname, avatarUrl) | |||
} | |||
// Auth 验证用户是否登录 | |||
func (*authApp) Auth(ctx context.Context, userId, deviceId int64, token string) error { | |||
return service.AuthService.Auth(ctx, userId, deviceId, token) | |||
} | |||
// AuthMaster 验证站长权限 | |||
func (*authApp) AuthMaster(ctx context.Context, masterId string) error { | |||
return service.AuthService.AuthMaster(ctx, masterId) | |||
} |
@@ -0,0 +1,72 @@ | |||
package app | |||
import ( | |||
"context" | |||
"gim/internal/business/comm/utils" | |||
"gim/internal/business/domain/user/repo" | |||
"gim/pkg/pb" | |||
"time" | |||
) | |||
type userApp struct{} | |||
var UserApp = new(userApp) | |||
func (*userApp) Get(ctx context.Context, userId int64) (*pb.User, error) { | |||
user, err := repo.UserRepo.Get(userId) | |||
return user.ToProto(), err | |||
} | |||
func (*userApp) Update(ctx context.Context, userId int64, req *pb.UpdateUserReq) error { | |||
utils.FilePutContents("update_info", utils.SerializeStr(map[string]interface{}{ | |||
"user_id": userId, | |||
"nickname": req.Nickname, | |||
"args": req, | |||
"time": time.Now().Format("2006-01-02 15:04:05"), | |||
})) | |||
u, err := repo.UserRepo.Get(userId) | |||
if err != nil { | |||
return err | |||
} | |||
if u == nil { | |||
return nil | |||
} | |||
u.Nickname = req.Nickname | |||
u.Sex = req.Sex | |||
u.AvatarUrl = req.AvatarUrl | |||
u.Extra = req.Extra | |||
u.UpdateTime = time.Now() | |||
err = repo.UserRepo.Save(u) | |||
if err != nil { | |||
return err | |||
} | |||
return nil | |||
} | |||
func (*userApp) GetByIds(ctx context.Context, userIds []int64) (map[int64]*pb.User, error) { | |||
users, err := repo.UserRepo.GetByIds(userIds) | |||
if err != nil { | |||
return nil, err | |||
} | |||
pbUsers := make(map[int64]*pb.User, len(users)) | |||
for i := range users { | |||
pbUsers[users[i].Id] = users[i].ToProto() | |||
} | |||
return pbUsers, nil | |||
} | |||
func (*userApp) Search(ctx context.Context, key string, masterId int64) ([]*pb.User, error) { | |||
users, err := repo.UserRepo.Search(key, masterId) | |||
if err != nil { | |||
return nil, err | |||
} | |||
pbUsers := make([]*pb.User, len(users)) | |||
for i, v := range users { | |||
pbUsers[i] = v.ToProto() | |||
} | |||
return pbUsers, nil | |||
} |
@@ -0,0 +1,25 @@ | |||
package app | |||
import ( | |||
"context" | |||
"fmt" | |||
svc "gim/internal/business/comm/service" | |||
"gim/pkg/grpclib" | |||
) | |||
type cloudUpload struct{} | |||
var CloudUpload = new(cloudUpload) | |||
// FileReqUpload 请求文件上传 | |||
func (*cloudUpload) FileReqUpload(ctx context.Context, uid, dirName, fName string, fSize int64) (interface{}, error) { | |||
masterId, _ := grpclib.GetCtxMasterId(ctx) | |||
callbackUrl := svc.SysCfgGet("-1", "cloud_file_upload_call_back_url") | |||
fileBucketHost := svc.SysCfgGet(masterId, "file_bucket_host") | |||
fileBucketScheme := svc.SysCfgGet("-1", "file_bucket_scheme") | |||
callbackUrl = fmt.Sprintf(callbackUrl, masterId, fileBucketHost, fileBucketScheme) | |||
return svc.FileReqUpload(ctx, uid, dirName, fName, callbackUrl, fSize) | |||
} |
@@ -0,0 +1,20 @@ | |||
package db | |||
import ( | |||
"gim/internal/business/comm/db/model" | |||
"gim/pkg/db" | |||
"gim/pkg/gerrors" | |||
) | |||
type dbEmoticon struct{} | |||
var DbEmoticon = new(dbEmoticon) | |||
// EmoticonFind 获取记录 | |||
func (*dbEmoticon) EmoticonFind(masterId string) (*[]model.Emoticon, error) { | |||
var list []model.Emoticon | |||
if err := db.DB.Where("`state` = 1 and `master_id` = ?", masterId).Order("sort desc").Find(&list).Error; err != nil { | |||
return nil, gerrors.WrapError(err) | |||
} | |||
return &list, nil | |||
} |
@@ -0,0 +1,119 @@ | |||
package db | |||
import ( | |||
"fmt" | |||
"gim/internal/business/comm/db/model" | |||
"gim/internal/business/comm/md" | |||
"gim/internal/business/comm/utils" | |||
"gim/internal/business/comm/utils/logx" | |||
"gim/pkg/db" | |||
"gim/pkg/gerrors" | |||
) | |||
type dbSysCfg struct{} | |||
var DbSysCfg = new(dbSysCfg) | |||
// SysCfgGetAll 获取系统配置 | |||
func (*dbSysCfg) SysCfgGetAll(masterId int64) (*[]model.SysCfg, error) { | |||
var cfgList []model.SysCfg | |||
if err := db.DB.Where("`master_id`=?", masterId).Find(&cfgList).Error; err != nil { | |||
return nil, gerrors.WrapError(err) | |||
} | |||
return &cfgList, nil | |||
} | |||
// SysCfgGetOne 获取一条记录 | |||
func (*dbSysCfg) SysCfgGetOne(key string, masterId int64) (*model.SysCfg, error) { | |||
var cfgList model.SysCfg | |||
if err := db.DB.Where("`key` = ? and `master_id` = ?", key, masterId).First(&cfgList).Error; err != nil { | |||
return nil, gerrors.WrapError(err) | |||
} | |||
return &cfgList, nil | |||
} | |||
// SysCfgInsert 插入一条配置信息 | |||
func (*dbSysCfg) SysCfgInsert(key, val, memo string, masterId int64) bool { | |||
cfg := model.SysCfg{Key: key, Val: val, Memo: memo, MasterId: masterId} | |||
err := db.DB.Create(&cfg).Error | |||
if err != nil { | |||
logx.Error(err) | |||
return false | |||
} | |||
return true | |||
} | |||
// SysCfgUpdate 修改某条配置信息 | |||
func (*dbSysCfg) SysCfgUpdate(key, val, memo string, masterId int64) bool { | |||
cfg := model.SysCfg{Key: key, Val: val, Memo: memo} | |||
err := db.DB.Where("`key` = ? and `master_id` = ?", key, masterId).Update(&cfg).Error | |||
if err != nil { | |||
logx.Error(err) | |||
return false | |||
} | |||
return true | |||
} | |||
// SysCfgGetWithDb 单条记录获取 | |||
func (*dbSysCfg) SysCfgGetWithDb(masterId string, HKey string) string { | |||
cacheKey := fmt.Sprintf(md.AppCfgCacheKey, masterId, HKey[0:1]) | |||
get, err := db.RedisUtil.HGetString(cacheKey, HKey) | |||
if err != nil || get == "" { | |||
cfg, err := DbSysCfg.SysCfgGetOne(HKey, utils.StrToInt64(masterId)) | |||
if err != nil || cfg == nil { | |||
_ = logx.Error(err) | |||
return "" | |||
} | |||
// key是否存在 | |||
cacheKeyExist := false | |||
if db.RedisUtil.Exists(cacheKey) { | |||
cacheKeyExist = true | |||
} | |||
// 设置缓存 | |||
_, err = db.RedisUtil.HSet(cacheKey, HKey, cfg.Val) | |||
if err != nil { | |||
_ = logx.Error(err) | |||
return "" | |||
} | |||
if !cacheKeyExist { // 如果是首次设置 设置过期时间 | |||
_, err := db.RedisUtil.Expire(cacheKey, md.CfgCacheTime) | |||
if err != nil { | |||
_ = logx.Error(err) | |||
return "" | |||
} | |||
} | |||
return cfg.Val | |||
} | |||
return get | |||
} | |||
// SysCfgFindWithDb 多条记录获取 | |||
func (*dbSysCfg) SysCfgFindWithDb(masterId string, keys ...string) map[string]string { | |||
res := map[string]string{} | |||
cacheKey := fmt.Sprintf(md.AppCfgCacheKey, masterId) | |||
err := db.RedisUtil.GetJson(cacheKey, &res) | |||
if err != nil || len(res) == 0 { | |||
cfgList, _ := DbSysCfg.SysCfgGetAll(utils.StrToInt64(masterId)) | |||
if cfgList == nil { | |||
return nil | |||
} | |||
for _, v := range *cfgList { | |||
res[v.Key] = v.Val | |||
} | |||
db.RedisUtil.SetJson(cacheKey, res, md.CfgCacheTime) | |||
} | |||
if len(keys) == 0 { | |||
return res | |||
} | |||
tmp := map[string]string{} | |||
for _, v := range keys { | |||
if val, ok := res[v]; ok { | |||
tmp[v] = val | |||
} else { | |||
tmp[v] = "" | |||
} | |||
} | |||
return tmp | |||
} |
@@ -0,0 +1,84 @@ | |||
package db | |||
import ( | |||
"fmt" | |||
"gim/internal/business/comm/db/model" | |||
"gim/internal/business/comm/md" | |||
"gim/internal/business/comm/utils" | |||
"gim/internal/business/comm/utils/logx" | |||
"gim/pkg/db" | |||
"gim/pkg/gerrors" | |||
"github.com/jinzhu/gorm" | |||
) | |||
type dbUserPushForJg struct{} | |||
var DbUserPushForJg = new(dbUserPushForJg) | |||
// UserPushForJgGetOne 获取一条记录 | |||
func (*dbUserPushForJg) UserPushForJgGetOne(uid string, masterId int64) (*model.UserPushForJg, error) { | |||
var cfgList model.UserPushForJg | |||
if err := db.DB.Where("`uid` = ? and `master_id` = ?", uid, masterId).First(&cfgList).Error; err != nil { | |||
if err == gorm.ErrRecordNotFound { | |||
return nil, nil | |||
} | |||
return nil, gerrors.WrapError(err) | |||
} | |||
return &cfgList, nil | |||
} | |||
// UserPushForJgInsert 插入一条配置信息 | |||
func (*dbUserPushForJg) UserPushForJgInsert(uid, masterId int64, pushAlia string) bool { | |||
cfg := model.UserPushForJg{Uid: uid, PushAlia: pushAlia, MasterId: masterId} | |||
err := db.DB.Create(&cfg).Error | |||
if err != nil { | |||
logx.Error(err) | |||
return false | |||
} | |||
return true | |||
} | |||
// UserPushForJgUpdate 修改某条配置信息 | |||
func (*dbUserPushForJg) UserPushForJgUpdate(uid, masterId int64, pushAlia string) bool { | |||
err := db.DB.Model(&model.UserPushForJg{}).Where("`uid` = ? and `master_id` = ?", uid, masterId).Update("push_alia", pushAlia).Error | |||
if err != nil { | |||
logx.Error(err) | |||
return false | |||
} | |||
return true | |||
} | |||
// UserPushForJgGetWithDb 单条记录获取 | |||
func (*dbUserPushForJg) UserPushForJgGetWithDb(masterId string, HKey string) string { | |||
cacheKey := fmt.Sprintf(md.AppUserPushForJgCacheKey, masterId, HKey[0:1]) | |||
get, err := db.RedisUtil.HGetString(cacheKey, HKey) | |||
if err != nil || get == "" { | |||
cfg, err := DbUserPushForJg.UserPushForJgGetOne(HKey, utils.StrToInt64(masterId)) | |||
if err != nil || cfg == nil { | |||
_ = logx.Error(err) | |||
return "" | |||
} | |||
// key是否存在 | |||
cacheKeyExist := false | |||
if db.RedisUtil.Exists(cacheKey) { | |||
cacheKeyExist = true | |||
} | |||
// 设置缓存 | |||
_, err = db.RedisUtil.HSet(cacheKey, HKey, cfg.PushAlia) | |||
if err != nil { | |||
_ = logx.Error(err) | |||
return "" | |||
} | |||
if !cacheKeyExist { // 如果是首次设置 设置过期时间 | |||
_, err := db.RedisUtil.Expire(cacheKey, md.CfgCacheTime) | |||
if err != nil { | |||
_ = logx.Error(err) | |||
return "" | |||
} | |||
} | |||
return cfg.PushAlia | |||
} | |||
return get | |||
} |
@@ -0,0 +1,28 @@ | |||
package model | |||
import "gim/pkg/pb" | |||
type Emoticon struct { | |||
Id int64 | |||
MasterId int64 // 站长id | |||
Name string // 名称 | |||
ImgUrl string // 图片地址 | |||
Memo string // 备注 | |||
Sort int // 排序 | |||
State int // 状态0关闭,1开启 | |||
CreateAt string // 创建时间 | |||
UpdateAt string // 更新时间 | |||
} | |||
func (e *Emoticon) ToProto() *pb.Emoticon { | |||
if e == nil { | |||
return nil | |||
} | |||
return &pb.Emoticon{ | |||
Name: e.Name, | |||
ImgUrl: e.ImgUrl, | |||
Memo: e.Memo, | |||
Sort: int32(e.Sort), | |||
} | |||
} |
@@ -0,0 +1,8 @@ | |||
package model | |||
type SysCfg struct { | |||
Key string // 键 | |||
Val string // 值 | |||
Memo string // 备注 | |||
MasterId int64 // 站长id | |||
} |
@@ -0,0 +1,8 @@ | |||
package model | |||
type UserPushForJg struct { | |||
Id int64 | |||
Uid int64 // 用户id | |||
PushAlia string // 用户头像 | |||
MasterId int64 // 站长id | |||
} |
@@ -0,0 +1,8 @@ | |||
package md | |||
// 缓存key统一管理, %s格式化为masterId | |||
const ( | |||
AppCfgCacheKey = "%s:gim_cfg_cache:%s" // 占位符: masterId, key的第一个字母 | |||
AppUserPushForJgCacheKey = "%s:gim_user_push_for_jg_cache:%s" // 占位符: masterId, key的第一个字母 | |||
CfgCacheTime = 0.5 * 60 * 60 | |||
) |
@@ -0,0 +1,17 @@ | |||
package md | |||
// 获取用户的缓存key | |||
const ( | |||
KEY_SYS_CFG_CACHE = "gim_sys_cfg_cache" | |||
// 文件缓存的key | |||
KEY_CFG_FILE_PVD = "file_provider" // 文件供应商 | |||
KEY_CFG_FILE_BUCKET = "file_bucket" | |||
KEY_CFG_FILE_REGION = "file_bucket_region" | |||
KEY_CFG_FILE_HOST = "file_bucket_host" | |||
KEY_CFG_FILE_SCHEME = "file_bucket_scheme" | |||
KEY_CFG_FILE_AK = "file_access_key" | |||
KEY_CFG_FILE_SK = "file_secret_key" | |||
KEY_CFG_FILE_MAX_SIZE = "file_user_upload_max_size" | |||
KEY_CFG_FILE_EXT = "file_ext" | |||
) |
@@ -0,0 +1,33 @@ | |||
package md | |||
// 用户拥有上传权限的目录, 目录ID | |||
const ( | |||
FILE_DIR_IMAGE = "image" | |||
FILE_DIR_VIDEO = "video" | |||
FILE_DIR_FILE = "file" | |||
) | |||
var ( | |||
FileUserDir = map[string]string{ | |||
FILE_DIR_IMAGE: "1", // 图片上传 | |||
FILE_DIR_VIDEO: "2", // 视频上传 | |||
FILE_DIR_FILE: "3", // 文件上传 | |||
} | |||
) | |||
// 文件回调信息 | |||
type FileCallback struct { | |||
Uid string `json:"uid"` | |||
DirId string `json:"dir_id"` | |||
Provider string `json:"provider"` // 供应商 | |||
FileName string `json:"fname"` // 原文件名 | |||
FileSize string `json:"fsize"` | |||
Hash string `json:"hash"` | |||
Bucket string `json:"bucket"` | |||
Mime string `json:"mime"` | |||
Width string `json:"w,omitempty"` | |||
Height string `json:"h,omitempty"` | |||
Time string `json:"time"` // 默认一个小时内要上传完毕,否则超时 | |||
Sign string `json:"sign"` // 签名 | |||
} |
@@ -0,0 +1,16 @@ | |||
package qiniu | |||
import ( | |||
"github.com/qiniu/api.v7/v7/auth" | |||
"github.com/qiniu/api.v7/v7/storage" | |||
) | |||
func BucketCreate() error { | |||
mac := auth.New(AK, SK) | |||
cfg := storage.Config{ | |||
// 是否使用https域名进行资源管理 | |||
UseHTTPS: false, | |||
} | |||
bucketManager := storage.NewBucketManager(mac, &cfg) | |||
return bucketManager.CreateBucket("", storage.RIDHuanan) | |||
} |
@@ -0,0 +1,18 @@ | |||
package qiniu | |||
import ( | |||
"github.com/qiniu/api.v7/v7/auth" | |||
"github.com/qiniu/api.v7/v7/storage" | |||
) | |||
func BucketDelete(bucketName string) error { | |||
mac := auth.New(AK, SK) | |||
cfg := storage.Config{ | |||
// 是否使用https域名进行资源管理 | |||
UseHTTPS: false, | |||
} | |||
bucketManager := storage.NewBucketManager(mac, &cfg) | |||
return bucketManager.DropBucket(bucketName) | |||
} |
@@ -0,0 +1,18 @@ | |||
package qiniu | |||
import ( | |||
"github.com/qiniu/api.v7/v7/auth" | |||
"github.com/qiniu/api.v7/v7/storage" | |||
) | |||
func BucketGetDomain(bucketName string) (string, error) { | |||
mac := auth.New(AK, SK) | |||
cfg := storage.Config{UseHTTPS: false} | |||
bucketManager := storage.NewBucketManager(mac, &cfg) | |||
b, err := bucketManager.ListBucketDomains(bucketName) | |||
if err != nil { | |||
return "", err | |||
} | |||
return b[0].Domain, nil | |||
} |
@@ -0,0 +1,22 @@ | |||
package qiniu | |||
import ( | |||
"gim/internal/business/comm/utils" | |||
) | |||
var ( | |||
AK = "MmxNdai23egjNUHjdzEVaTPdPCIbWzENz9BQuak3" | |||
SK = "mElaFlM9O16rXp-ihoQdJ9KOH56naKm3MoyQBA59" | |||
BUCKET = "dev-fnuoos" // 桶子名称 | |||
BUCKET_SCHEME = "http" | |||
BUCKET_REGION = "up-z2.qiniup.com" | |||
Expires uint64 = 3600 | |||
) | |||
func Init(ak, sk, bucket, region, scheme string) { | |||
AK, SK, BUCKET, BUCKET_REGION, BUCKET_SCHEME = ak, sk, bucket, region, scheme | |||
} | |||
func Sign(t string) string { | |||
return utils.Md5(AK + SK + t) | |||
} |
@@ -0,0 +1,42 @@ | |||
package qiniu | |||
import ( | |||
"gim/internal/business/comm/md" | |||
"gim/internal/business/comm/utils" | |||
"time" | |||
"github.com/qiniu/api.v7/v7/auth/qbox" | |||
_ "github.com/qiniu/api.v7/v7/conf" | |||
"github.com/qiniu/api.v7/v7/storage" | |||
) | |||
// 请求图片上传地址信息 | |||
func ReqImgUpload(f *md.FileCallback, callbackUrl string) interface{} { | |||
if ext := utils.FileExt(f.FileName); ext == "png" || ext == "jpg" || ext == "jpeg" || ext == "gif" || ext == "bmp" || ext == "webp" { | |||
f.Width = "$(imageInfo.width)" | |||
f.Height = "$(imageInfo.height)" | |||
} | |||
f.Provider = "qiniu" | |||
f.FileSize = "$(fsize)" | |||
f.Hash = "$(etag)" | |||
f.Bucket = "$(bucket)" | |||
f.Mime = "$(mimeType)" | |||
f.Time = utils.Int64ToStr(time.Now().Unix()) | |||
f.Sign = Sign(f.Time) | |||
putPolicy := storage.PutPolicy{ | |||
Scope: BUCKET + ":" + f.FileName, // 使用覆盖方式时候必须请求里面有key,否则报错 | |||
Expires: Expires, | |||
ForceSaveKey: true, | |||
SaveKey: f.FileName, | |||
//MimeLimit: "image/*", // 只允许上传图片 | |||
CallbackURL: callbackUrl, | |||
CallbackBody: utils.SerializeStr(f), | |||
CallbackBodyType: "application/json", | |||
} | |||
return &struct { | |||
Method string `json:"method"` | |||
Key string `json:"key"` | |||
Host string `json:"host"` | |||
Token string `json:"token"` | |||
}{Key: f.FileName, Method: "POST", Host: BUCKET_SCHEME + "://" + BUCKET_REGION, Token: putPolicy.UploadToken(qbox.NewMac(AK, SK))} | |||
} |
@@ -0,0 +1,92 @@ | |||
package svc | |||
import ( | |||
"context" | |||
"errors" | |||
"fmt" | |||
"gim/internal/business/comm/md" | |||
"gim/internal/business/comm/service/qiniu" | |||
"gim/internal/business/comm/utils" | |||
"gim/internal/business/comm/utils/logx" | |||
"strings" | |||
) | |||
// 请求文件上传 | |||
func FileReqUpload(ctx context.Context, uid, dirName, fName, callbackUrl string, fSize int64) (interface{}, error) { | |||
ext := utils.FileExt(fName) | |||
if err := initStg(ctx, fSize, ext); err != nil { | |||
return nil, err | |||
} | |||
newName := dirName + "_" + fmt.Sprintf("%010s", uid) | |||
// 默认都加时间戳 | |||
newName += "_" + utils.FormatNanoUnix() + utils.RandString(4, "0123456789") | |||
newName += "." + ext | |||
f := &md.FileCallback{ | |||
Uid: uid, | |||
DirId: md.FileUserDir[dirName], | |||
FileName: newName, | |||
} | |||
return qiniu.ReqImgUpload(f, callbackUrl), nil | |||
} | |||
func initStg(ctx context.Context, fSize int64, ext string) error { | |||
// 获取上传配置 | |||
stgInfo := SysCfgFind( | |||
ctx, | |||
md.KEY_CFG_FILE_BUCKET, | |||
md.KEY_CFG_FILE_HOST, | |||
md.KEY_CFG_FILE_AK, | |||
md.KEY_CFG_FILE_SK, | |||
md.KEY_CFG_FILE_PVD, | |||
md.KEY_CFG_FILE_REGION, | |||
md.KEY_CFG_FILE_MAX_SIZE, | |||
md.KEY_CFG_FILE_EXT, | |||
md.KEY_CFG_FILE_SCHEME, | |||
) | |||
if stgInfo == nil { | |||
return errors.New("获取配置信息出错") | |||
} | |||
// todo 目前仅支持七牛 | |||
if v, ok := stgInfo[md.KEY_CFG_FILE_PVD]; !ok || v != "qiniu" { | |||
return errors.New("file_provider error") | |||
} | |||
if v, ok := stgInfo[md.KEY_CFG_FILE_REGION]; !ok || v == "" { | |||
return errors.New("file_bucket_region error") | |||
} | |||
if v, ok := stgInfo[md.KEY_CFG_FILE_AK]; !ok || v == "" { | |||
return errors.New("file_access_key error") | |||
} | |||
if v, ok := stgInfo[md.KEY_CFG_FILE_SK]; !ok || v == "" { | |||
return errors.New("file_secret_key error") | |||
} | |||
if v, ok := stgInfo[md.KEY_CFG_FILE_BUCKET]; !ok || v == "" { | |||
return errors.New("file_bucket error") | |||
} | |||
if v, ok := stgInfo[md.KEY_CFG_FILE_SCHEME]; !ok || v == "" { | |||
stgInfo[md.KEY_CFG_FILE_SCHEME] = "http" | |||
SysCfgSet(ctx, md.KEY_CFG_FILE_SCHEME, stgInfo[md.KEY_CFG_FILE_SCHEME], "文件域名HTTP协议") | |||
} | |||
qiniu.Init(stgInfo[md.KEY_CFG_FILE_AK], stgInfo[md.KEY_CFG_FILE_SK], stgInfo[md.KEY_CFG_FILE_BUCKET], stgInfo[md.KEY_CFG_FILE_REGION], stgInfo[md.KEY_CFG_FILE_SCHEME]) | |||
if v, ok := stgInfo[md.KEY_CFG_FILE_HOST]; !ok || v == "" { | |||
var err error | |||
stgInfo[md.KEY_CFG_FILE_HOST], err = qiniu.BucketGetDomain(stgInfo[md.KEY_CFG_FILE_BUCKET]) | |||
if err != nil { | |||
logx.Error(err) | |||
return errors.New("服务器配置错误") | |||
} | |||
SysCfgSet(ctx, md.KEY_CFG_FILE_HOST, stgInfo[md.KEY_CFG_FILE_HOST], "文件域名地址") | |||
} | |||
// 检查文件大小限制 | |||
if v, ok := stgInfo[md.KEY_CFG_FILE_MAX_SIZE]; ok && v != "" && utils.StrToInt64(v) < fSize { | |||
return errors.New("file_user_upload_max_size error") | |||
} | |||
// 检查文件后缀 | |||
if v, ok := stgInfo[md.KEY_CFG_FILE_EXT]; ok && v != "" && !strings.Contains(v, ext) { | |||
return errors.New("file_ext error") | |||
} | |||
return nil | |||
} |
@@ -0,0 +1,106 @@ | |||
package svc | |||
import ( | |||
"context" | |||
"fmt" | |||
"gim/internal/business/comm/db" | |||
"gim/internal/business/comm/md" | |||
"gim/internal/business/comm/utils" | |||
db2 "gim/pkg/db" | |||
"gim/pkg/grpclib" | |||
) | |||
// 单挑记录获取 | |||
func SysCfgGet(masterId, key string) string { | |||
return db.DbSysCfg.SysCfgGetWithDb(masterId, key) | |||
} | |||
// 多条记录获取 | |||
func SysCfgFind(ctx context.Context, keys ...string) map[string]string { | |||
masterId, _ := grpclib.GetCtxMasterId(ctx) | |||
tmp := SysCfgFindComm(masterId, keys...) | |||
return tmp | |||
} | |||
// SysCfgGetByMasterId get one config by master id | |||
func SysCfgGetByMasterId(masterId, key string) string { | |||
res := SysCfgFindComm(masterId, key) | |||
if _, ok := res[key]; !ok { | |||
return "" | |||
} | |||
return res[key] | |||
} | |||
// SysCfgFindComm get cfg by master id | |||
func SysCfgFindComm(masterId string, keys ...string) map[string]string { | |||
res := map[string]string{} | |||
cfgKey := fmt.Sprintf("%s:gim_cfg_cache", masterId) | |||
err := db2.RedisUtil.GetJson(cfgKey, &res) | |||
if err != nil || len(res) == 0 { | |||
cfgList, _ := db.DbSysCfg.SysCfgGetAll(utils.StrToInt64(masterId)) | |||
if cfgList == nil { | |||
return nil | |||
} | |||
for _, v := range *cfgList { | |||
res[v.Key] = v.Val | |||
} | |||
db2.RedisUtil.SetJson(cfgKey, res, 1800) | |||
} | |||
if len(keys) == 0 { | |||
return res | |||
} | |||
tmp := map[string]string{} | |||
for _, v := range keys { | |||
if val, ok := res[v]; ok { | |||
tmp[v] = val | |||
} else { | |||
tmp[v] = "" | |||
} | |||
} | |||
return tmp | |||
} | |||
// EgSysCfgFind 多条记录获取 | |||
func EgSysCfgFind(masterId string, keys ...string) map[string]string { | |||
res := map[string]string{} | |||
if len(res) == 0 { | |||
cfgList, _ := db.DbSysCfg.SysCfgGetAll(utils.StrToInt64(masterId)) | |||
if cfgList == nil { | |||
return nil | |||
} | |||
for _, v := range *cfgList { | |||
res[v.Key] = v.Val | |||
} | |||
} | |||
if len(keys) == 0 { | |||
return res | |||
} | |||
tmp := map[string]string{} | |||
for _, v := range keys { | |||
if val, ok := res[v]; ok { | |||
tmp[v] = val | |||
} else { | |||
tmp[v] = "" | |||
} | |||
} | |||
return tmp | |||
} | |||
// SysCfgCleanCache 清理系统配置信息 | |||
func SysCfgCleanCache() { | |||
db2.RedisUtil.Del(md.KEY_SYS_CFG_CACHE) | |||
} | |||
// SysCfgSet 写入系统设置 | |||
func SysCfgSet(ctx context.Context, key, val, memo string) bool { | |||
masterId, _ := grpclib.GetCtxMasterId(ctx) | |||
cfg, err := db.DbSysCfg.SysCfgGetOne(key, utils.StrToInt64(masterId)) | |||
if err != nil || cfg == nil { | |||
return db.DbSysCfg.SysCfgInsert(key, val, memo, utils.StrToInt64(masterId)) | |||
} | |||
if memo != "" && cfg.Memo != memo { | |||
cfg.Memo = memo | |||
} | |||
SysCfgCleanCache() | |||
return db.DbSysCfg.SysCfgUpdate(key, val, cfg.Memo, utils.StrToInt64(masterId)) | |||
} |
@@ -0,0 +1,117 @@ | |||
package utils | |||
import ( | |||
"encoding/base64" | |||
"fmt" | |||
"regexp" | |||
) | |||
const ( | |||
Base64Std = iota | |||
Base64Url | |||
Base64RawStd | |||
Base64RawUrl | |||
) | |||
func Base64StdEncode(str interface{}) string { | |||
return Base64Encode(str, Base64Std) | |||
} | |||
func Base64StdDecode(str interface{}) string { | |||
return Base64Decode(str, Base64Std) | |||
} | |||
func Base64UrlEncode(str interface{}) string { | |||
return Base64Encode(str, Base64Url) | |||
} | |||
func Base64UrlDecode(str interface{}) string { | |||
return Base64Decode(str, Base64Url) | |||
} | |||
func Base64RawStdEncode(str interface{}) string { | |||
return Base64Encode(str, Base64RawStd) | |||
} | |||
func Base64RawStdDecode(str interface{}) string { | |||
return Base64Decode(str, Base64RawStd) | |||
} | |||
func Base64RawUrlEncode(str interface{}) string { | |||
return Base64Encode(str, Base64RawUrl) | |||
} | |||
func Base64RawUrlDecode(str interface{}) string { | |||
return Base64Decode(str, Base64RawUrl) | |||
} | |||
func Base64Encode(str interface{}, encode int) string { | |||
newEncode := base64Encode(encode) | |||
if newEncode == nil { | |||
return "" | |||
} | |||
switch v := str.(type) { | |||
case string: | |||
return newEncode.EncodeToString([]byte(v)) | |||
case []byte: | |||
return newEncode.EncodeToString(v) | |||
} | |||
return newEncode.EncodeToString([]byte(fmt.Sprint(str))) | |||
} | |||
func Base64Decode(str interface{}, encode int) string { | |||
var err error | |||
var b []byte | |||
newEncode := base64Encode(encode) | |||
if newEncode == nil { | |||
return "" | |||
} | |||
switch v := str.(type) { | |||
case string: | |||
b, err = newEncode.DecodeString(v) | |||
case []byte: | |||
b, err = newEncode.DecodeString(string(v)) | |||
default: | |||
return "" | |||
} | |||
if err != nil { | |||
return "" | |||
} | |||
return string(b) | |||
} | |||
func base64Encode(encode int) *base64.Encoding { | |||
switch encode { | |||
case Base64Std: | |||
return base64.StdEncoding | |||
case Base64Url: | |||
return base64.URLEncoding | |||
case Base64RawStd: | |||
return base64.RawStdEncoding | |||
case Base64RawUrl: | |||
return base64.RawURLEncoding | |||
default: | |||
return nil | |||
} | |||
} | |||
func JudgeBase64(str string) bool { | |||
pattern := "^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)$" | |||
matched, err := regexp.MatchString(pattern, str) | |||
if err != nil { | |||
return false | |||
} | |||
if !(len(str)%4 == 0 && matched) { | |||
return false | |||
} | |||
unCodeStr, err := base64.StdEncoding.DecodeString(str) | |||
if err != nil { | |||
return false | |||
} | |||
tranStr := base64.StdEncoding.EncodeToString(unCodeStr) | |||
//return str==base64.StdEncoding.EncodeToString(unCodeStr) | |||
if str == tranStr { | |||
return true | |||
} | |||
return false | |||
} |
@@ -0,0 +1,369 @@ | |||
package utils | |||
import ( | |||
"encoding/binary" | |||
"encoding/json" | |||
"fmt" | |||
"math" | |||
"strconv" | |||
"strings" | |||
) | |||
func ToString(raw interface{}, e error) (res string) { | |||
if e != nil { | |||
return "" | |||
} | |||
return AnyToString(raw) | |||
} | |||
func ToInt64(raw interface{}, e error) int64 { | |||
if e != nil { | |||
return 0 | |||
} | |||
return AnyToInt64(raw) | |||
} | |||
func Float64ToStrPrec4(f float64) string { | |||
return strconv.FormatFloat(f, 'f', 4, 64) | |||
} | |||
func AnyToBool(raw interface{}) bool { | |||
switch i := raw.(type) { | |||
case float32, float64, int, int64, uint, uint8, uint16, uint32, uint64, int8, int16, int32: | |||
return i != 0 | |||
case []byte: | |||
return i != nil | |||
case string: | |||
if i == "false" { | |||
return false | |||
} | |||
return i != "" | |||
case error: | |||
return false | |||
case nil: | |||
return true | |||
} | |||
val := fmt.Sprint(raw) | |||
val = strings.TrimLeft(val, "&") | |||
if strings.TrimLeft(val, "{}") == "" { | |||
return false | |||
} | |||
if strings.TrimLeft(val, "[]") == "" { | |||
return false | |||
} | |||
// ptr type | |||
b, err := json.Marshal(raw) | |||
if err != nil { | |||
return false | |||
} | |||
if strings.TrimLeft(string(b), "\"\"") == "" { | |||
return false | |||
} | |||
if strings.TrimLeft(string(b), "{}") == "" { | |||
return false | |||
} | |||
return true | |||
} | |||
func AnyToInt64(raw interface{}) int64 { | |||
switch i := raw.(type) { | |||
case string: | |||
res, _ := strconv.ParseInt(i, 10, 64) | |||
return res | |||
case []byte: | |||
return BytesToInt64(i) | |||
case int: | |||
return int64(i) | |||
case int64: | |||
return i | |||
case uint: | |||
return int64(i) | |||
case uint8: | |||
return int64(i) | |||
case uint16: | |||
return int64(i) | |||
case uint32: | |||
return int64(i) | |||
case uint64: | |||
return int64(i) | |||
case int8: | |||
return int64(i) | |||
case int16: | |||
return int64(i) | |||
case int32: | |||
return int64(i) | |||
case float32: | |||
return int64(i) | |||
case float64: | |||
return int64(i) | |||
case error: | |||
return 0 | |||
case bool: | |||
if i { | |||
return 1 | |||
} | |||
return 0 | |||
} | |||
return 0 | |||
} | |||
func AnyToString(raw interface{}) string { | |||
switch i := raw.(type) { | |||
case []byte: | |||
return string(i) | |||
case int: | |||
return strconv.FormatInt(int64(i), 10) | |||
case int64: | |||
return strconv.FormatInt(i, 10) | |||
case float32: | |||
return Float64ToStr(float64(i)) | |||
case float64: | |||
return Float64ToStr(i) | |||
case uint: | |||
return strconv.FormatInt(int64(i), 10) | |||
case uint8: | |||
return strconv.FormatInt(int64(i), 10) | |||
case uint16: | |||
return strconv.FormatInt(int64(i), 10) | |||
case uint32: | |||
return strconv.FormatInt(int64(i), 10) | |||
case uint64: | |||
return strconv.FormatInt(int64(i), 10) | |||
case int8: | |||
return strconv.FormatInt(int64(i), 10) | |||
case int16: | |||
return strconv.FormatInt(int64(i), 10) | |||
case int32: | |||
return strconv.FormatInt(int64(i), 10) | |||
case string: | |||
return i | |||
case error: | |||
return i.Error() | |||
case bool: | |||
return strconv.FormatBool(i) | |||
} | |||
return fmt.Sprintf("%#v", raw) | |||
} | |||
func AnyToFloat64(raw interface{}) float64 { | |||
switch i := raw.(type) { | |||
case []byte: | |||
f, _ := strconv.ParseFloat(string(i), 64) | |||
return f | |||
case int: | |||
return float64(i) | |||
case int64: | |||
return float64(i) | |||
case float32: | |||
return float64(i) | |||
case float64: | |||
return i | |||
case uint: | |||
return float64(i) | |||
case uint8: | |||
return float64(i) | |||
case uint16: | |||
return float64(i) | |||
case uint32: | |||
return float64(i) | |||
case uint64: | |||
return float64(i) | |||
case int8: | |||
return float64(i) | |||
case int16: | |||
return float64(i) | |||
case int32: | |||
return float64(i) | |||
case string: | |||
f, _ := strconv.ParseFloat(i, 64) | |||
return f | |||
case bool: | |||
if i { | |||
return 1 | |||
} | |||
} | |||
return 0 | |||
} | |||
func ToByte(raw interface{}, e error) []byte { | |||
if e != nil { | |||
return []byte{} | |||
} | |||
switch i := raw.(type) { | |||
case string: | |||
return []byte(i) | |||
case int: | |||
return Int64ToBytes(int64(i)) | |||
case int64: | |||
return Int64ToBytes(i) | |||
case float32: | |||
return Float32ToByte(i) | |||
case float64: | |||
return Float64ToByte(i) | |||
case uint: | |||
return Int64ToBytes(int64(i)) | |||
case uint8: | |||
return Int64ToBytes(int64(i)) | |||
case uint16: | |||
return Int64ToBytes(int64(i)) | |||
case uint32: | |||
return Int64ToBytes(int64(i)) | |||
case uint64: | |||
return Int64ToBytes(int64(i)) | |||
case int8: | |||
return Int64ToBytes(int64(i)) | |||
case int16: | |||
return Int64ToBytes(int64(i)) | |||
case int32: | |||
return Int64ToBytes(int64(i)) | |||
case []byte: | |||
return i | |||
case error: | |||
return []byte(i.Error()) | |||
case bool: | |||
if i { | |||
return []byte("true") | |||
} | |||
return []byte("false") | |||
} | |||
return []byte(fmt.Sprintf("%#v", raw)) | |||
} | |||
func Int64ToBytes(i int64) []byte { | |||
var buf = make([]byte, 8) | |||
binary.BigEndian.PutUint64(buf, uint64(i)) | |||
return buf | |||
} | |||
func BytesToInt64(buf []byte) int64 { | |||
return int64(binary.BigEndian.Uint64(buf)) | |||
} | |||
func StrToInt(s string) int { | |||
res, _ := strconv.Atoi(s) | |||
return res | |||
} | |||
func StrToInt64(s string) int64 { | |||
res, _ := strconv.ParseInt(s, 10, 64) | |||
return res | |||
} | |||
func Float32ToByte(float float32) []byte { | |||
bits := math.Float32bits(float) | |||
bytes := make([]byte, 4) | |||
binary.LittleEndian.PutUint32(bytes, bits) | |||
return bytes | |||
} | |||
func ByteToFloat32(bytes []byte) float32 { | |||
bits := binary.LittleEndian.Uint32(bytes) | |||
return math.Float32frombits(bits) | |||
} | |||
func Float64ToByte(float float64) []byte { | |||
bits := math.Float64bits(float) | |||
bytes := make([]byte, 8) | |||
binary.LittleEndian.PutUint64(bytes, bits) | |||
return bytes | |||
} | |||
func ByteToFloat64(bytes []byte) float64 { | |||
bits := binary.LittleEndian.Uint64(bytes) | |||
return math.Float64frombits(bits) | |||
} | |||
func Float64ToStr(f float64) string { | |||
return strconv.FormatFloat(f, 'f', 2, 64) | |||
} | |||
func Float64ToStrPrec1(f float64) string { | |||
return strconv.FormatFloat(f, 'f', 1, 64) | |||
} | |||
func Float64ToStrByPrec(f float64, prec int) string { | |||
return strconv.FormatFloat(f, 'f', prec, 64) | |||
} | |||
func Float32ToStr(f float32) string { | |||
return Float64ToStr(float64(f)) | |||
} | |||
func StrToFloat64(s string) float64 { | |||
res, err := strconv.ParseFloat(s, 64) | |||
if err != nil { | |||
return 0 | |||
} | |||
return res | |||
} | |||
func StrToFormat(s string, prec int) string { | |||
ex := strings.Split(s, ".") | |||
if len(ex) == 2 { | |||
if StrToFloat64(ex[1]) == 0 { //小数点后面为空就是不要小数点了 | |||
return ex[0] | |||
} | |||
//看取多少位 | |||
str := ex[1] | |||
str1 := str | |||
if prec < len(str) { | |||
str1 = str[0:prec] | |||
} else { | |||
for i := 0; i < prec-len(str); i++ { | |||
str1 += "0" | |||
} | |||
} | |||
if prec > 0 { | |||
return ex[0] + "." + str1 | |||
} else { | |||
return ex[0] | |||
} | |||
} | |||
return s | |||
} | |||
func StrToFloat32(s string) float32 { | |||
res, err := strconv.ParseFloat(s, 32) | |||
if err != nil { | |||
return 0 | |||
} | |||
return float32(res) | |||
} | |||
func StrToBool(s string) bool { | |||
b, _ := strconv.ParseBool(s) | |||
return b | |||
} | |||
func BoolToStr(b bool) string { | |||
if b { | |||
return "true" | |||
} | |||
return "false" | |||
} | |||
func FloatToInt64(f float64) int64 { | |||
return int64(f) | |||
} | |||
func IntToStr(i int) string { | |||
return strconv.Itoa(i) | |||
} | |||
func Int64ToStr(i int64) string { | |||
return strconv.FormatInt(i, 10) | |||
} | |||
func IntToFloat64(i int) float64 { | |||
s := strconv.Itoa(i) | |||
res, err := strconv.ParseFloat(s, 64) | |||
if err != nil { | |||
return 0 | |||
} | |||
return res | |||
} | |||
func Int64ToFloat64(i int64) float64 { | |||
s := strconv.FormatInt(i, 10) | |||
res, err := strconv.ParseFloat(s, 64) | |||
if err != nil { | |||
return 0 | |||
} | |||
return res | |||
} |
@@ -0,0 +1,209 @@ | |||
package utils | |||
import ( | |||
"bytes" | |||
"crypto/tls" | |||
"fmt" | |||
"io" | |||
"io/ioutil" | |||
"net/http" | |||
"net/url" | |||
"sort" | |||
"strings" | |||
"time" | |||
) | |||
var CurlDebug bool | |||
func CurlGet(router string, header map[string]string) ([]byte, error) { | |||
return curl(http.MethodGet, router, nil, header) | |||
} | |||
func CurlGetJson(router string, body interface{}, header map[string]string) ([]byte, error) { | |||
return curl_new(http.MethodGet, router, body, header) | |||
} | |||
// 只支持form 与json 提交, 请留意body的类型, 支持string, []byte, map[string]string | |||
func CurlPost(router string, body interface{}, header map[string]string) ([]byte, error) { | |||
return curl(http.MethodPost, router, body, header) | |||
} | |||
func CurlPut(router string, body interface{}, header map[string]string) ([]byte, error) { | |||
return curl(http.MethodPut, router, body, header) | |||
} | |||
// 只支持form 与json 提交, 请留意body的类型, 支持string, []byte, map[string]string | |||
func CurlPatch(router string, body interface{}, header map[string]string) ([]byte, error) { | |||
return curl(http.MethodPatch, router, body, header) | |||
} | |||
// CurlDelete is curl delete | |||
func CurlDelete(router string, body interface{}, header map[string]string) ([]byte, error) { | |||
return curl(http.MethodDelete, router, body, header) | |||
} | |||
func curl(method, router string, body interface{}, header map[string]string) ([]byte, error) { | |||
var reqBody io.Reader | |||
contentType := "application/json" | |||
switch v := body.(type) { | |||
case string: | |||
reqBody = strings.NewReader(v) | |||
case []byte: | |||
reqBody = bytes.NewReader(v) | |||
case map[string]string: | |||
val := url.Values{} | |||
for k, v := range v { | |||
val.Set(k, v) | |||
} | |||
reqBody = strings.NewReader(val.Encode()) | |||
contentType = "application/x-www-form-urlencoded" | |||
case map[string]interface{}: | |||
val := url.Values{} | |||
for k, v := range v { | |||
val.Set(k, v.(string)) | |||
} | |||
reqBody = strings.NewReader(val.Encode()) | |||
contentType = "application/x-www-form-urlencoded" | |||
} | |||
if header == nil { | |||
header = map[string]string{"Content-Type": contentType} | |||
} | |||
if _, ok := header["Content-Type"]; !ok { | |||
header["Content-Type"] = contentType | |||
} | |||
resp, er := CurlReq(method, router, reqBody, header) | |||
if er != nil { | |||
return nil, er | |||
} | |||
res, err := ioutil.ReadAll(resp.Body) | |||
if CurlDebug { | |||
blob := SerializeStr(body) | |||
if contentType != "application/json" { | |||
blob = HttpBuild(body) | |||
} | |||
fmt.Printf("\n\n=====================\n[url]: %s\n[time]: %s\n[method]: %s\n[content-type]: %v\n[req_header]: %s\n[req_body]: %#v\n[resp_err]: %v\n[resp_header]: %v\n[resp_body]: %v\n=====================\n\n", | |||
router, | |||
time.Now().Format("2006-01-02 15:04:05.000"), | |||
method, | |||
contentType, | |||
HttpBuildQuery(header), | |||
blob, | |||
err, | |||
SerializeStr(resp.Header), | |||
string(res), | |||
) | |||
} | |||
resp.Body.Close() | |||
return res, err | |||
} | |||
func curl_new(method, router string, body interface{}, header map[string]string) ([]byte, error) { | |||
var reqBody io.Reader | |||
contentType := "application/json" | |||
if header == nil { | |||
header = map[string]string{"Content-Type": contentType} | |||
} | |||
if _, ok := header["Content-Type"]; !ok { | |||
header["Content-Type"] = contentType | |||
} | |||
resp, er := CurlReq(method, router, reqBody, header) | |||
if er != nil { | |||
return nil, er | |||
} | |||
res, err := ioutil.ReadAll(resp.Body) | |||
if CurlDebug { | |||
blob := SerializeStr(body) | |||
if contentType != "application/json" { | |||
blob = HttpBuild(body) | |||
} | |||
fmt.Printf("\n\n=====================\n[url]: %s\n[time]: %s\n[method]: %s\n[content-type]: %v\n[req_header]: %s\n[req_body]: %#v\n[resp_err]: %v\n[resp_header]: %v\n[resp_body]: %v\n=====================\n\n", | |||
router, | |||
time.Now().Format("2006-01-02 15:04:05.000"), | |||
method, | |||
contentType, | |||
HttpBuildQuery(header), | |||
blob, | |||
err, | |||
SerializeStr(resp.Header), | |||
string(res), | |||
) | |||
} | |||
resp.Body.Close() | |||
return res, err | |||
} | |||
func CurlReq(method, router string, reqBody io.Reader, header map[string]string) (*http.Response, error) { | |||
req, _ := http.NewRequest(method, router, reqBody) | |||
if header != nil { | |||
for k, v := range header { | |||
req.Header.Set(k, v) | |||
} | |||
} | |||
// 绕过github等可能因为特征码返回503问题 | |||
// https://www.imwzk.com/posts/2021-03-14-why-i-always-get-503-with-golang/ | |||
defaultCipherSuites := []uint16{0xc02f, 0xc030, 0xc02b, 0xc02c, 0xcca8, 0xcca9, 0xc013, 0xc009, | |||
0xc014, 0xc00a, 0x009c, 0x009d, 0x002f, 0x0035, 0xc012, 0x000a} | |||
client := &http.Client{ | |||
Transport: &http.Transport{ | |||
TLSClientConfig: &tls.Config{ | |||
InsecureSkipVerify: true, | |||
CipherSuites: append(defaultCipherSuites[8:], defaultCipherSuites[:8]...), | |||
}, | |||
}, | |||
// 获取301重定向 | |||
CheckRedirect: func(req *http.Request, via []*http.Request) error { | |||
return http.ErrUseLastResponse | |||
}, | |||
} | |||
return client.Do(req) | |||
} | |||
// 组建get请求参数,sortAsc true为小到大,false为大到小,nil不排序 a=123&b=321 | |||
func HttpBuildQuery(args map[string]string, sortAsc ...bool) string { | |||
str := "" | |||
if len(args) == 0 { | |||
return str | |||
} | |||
if len(sortAsc) > 0 { | |||
keys := make([]string, 0, len(args)) | |||
for k := range args { | |||
keys = append(keys, k) | |||
} | |||
if sortAsc[0] { | |||
sort.Strings(keys) | |||
} else { | |||
sort.Sort(sort.Reverse(sort.StringSlice(keys))) | |||
} | |||
for _, k := range keys { | |||
str += "&" + k + "=" + args[k] | |||
} | |||
} else { | |||
for k, v := range args { | |||
str += "&" + k + "=" + v | |||
} | |||
} | |||
return str[1:] | |||
} | |||
func HttpBuild(body interface{}, sortAsc ...bool) string { | |||
params := map[string]string{} | |||
if args, ok := body.(map[string]interface{}); ok { | |||
for k, v := range args { | |||
params[k] = AnyToString(v) | |||
} | |||
return HttpBuildQuery(params, sortAsc...) | |||
} | |||
if args, ok := body.(map[string]string); ok { | |||
for k, v := range args { | |||
params[k] = AnyToString(v) | |||
} | |||
return HttpBuildQuery(params, sortAsc...) | |||
} | |||
if args, ok := body.(map[string]int); ok { | |||
for k, v := range args { | |||
params[k] = AnyToString(v) | |||
} | |||
return HttpBuildQuery(params, sortAsc...) | |||
} | |||
return AnyToString(body) | |||
} |
@@ -0,0 +1,22 @@ | |||
package utils | |||
import ( | |||
"os" | |||
"path" | |||
"strings" | |||
"time" | |||
) | |||
// 获取文件后缀 | |||
func FileExt(fname string) string { | |||
return strings.ToLower(strings.TrimLeft(path.Ext(fname), ".")) | |||
} | |||
func FilePutContents(fileName string, content string) { | |||
fd, _ := os.OpenFile("./tmp/"+fileName+".log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644) | |||
fd_time := time.Now().Format("2006-01-02 15:04:05") | |||
fd_content := strings.Join([]string{"[", fd_time, "] ", content, "\n"}, "") | |||
buf := []byte(fd_content) | |||
fd.Write(buf) | |||
fd.Close() | |||
} |
@@ -0,0 +1,245 @@ | |||
package logx | |||
import ( | |||
"os" | |||
"strings" | |||
"time" | |||
"go.uber.org/zap" | |||
"go.uber.org/zap/zapcore" | |||
) | |||
type LogConfig struct { | |||
AppName string `yaml:"app_name" json:"app_name" toml:"app_name"` | |||
Level string `yaml:"level" json:"level" toml:"level"` | |||
StacktraceLevel string `yaml:"stacktrace_level" json:"stacktrace_level" toml:"stacktrace_level"` | |||
IsStdOut bool `yaml:"is_stdout" json:"is_stdout" toml:"is_stdout"` | |||
TimeFormat string `yaml:"time_format" json:"time_format" toml:"time_format"` // second, milli, nano, standard, iso, | |||
Encoding string `yaml:"encoding" json:"encoding" toml:"encoding"` // console, json | |||
Skip int `yaml:"skip" json:"skip" toml:"skip"` | |||
IsFileOut bool `yaml:"is_file_out" json:"is_file_out" toml:"is_file_out"` | |||
FileDir string `yaml:"file_dir" json:"file_dir" toml:"file_dir"` | |||
FileName string `yaml:"file_name" json:"file_name" toml:"file_name"` | |||
FileMaxSize int `yaml:"file_max_size" json:"file_max_size" toml:"file_max_size"` | |||
FileMaxAge int `yaml:"file_max_age" json:"file_max_age" toml:"file_max_age"` | |||
} | |||
var ( | |||
l *LogX = defaultLogger() | |||
conf *LogConfig | |||
) | |||
// default logger setting | |||
func defaultLogger() *LogX { | |||
conf = &LogConfig{ | |||
Level: "debug", | |||
StacktraceLevel: "error", | |||
IsStdOut: true, | |||
TimeFormat: "standard", | |||
Encoding: "console", | |||
Skip: 2, | |||
} | |||
writers := []zapcore.WriteSyncer{os.Stdout} | |||
lg, lv := newZapLogger(setLogLevel(conf.Level), setLogLevel(conf.StacktraceLevel), conf.Encoding, conf.TimeFormat, conf.Skip, zapcore.NewMultiWriteSyncer(writers...)) | |||
zap.RedirectStdLog(lg) | |||
return &LogX{logger: lg, atomLevel: lv} | |||
} | |||
// initial standard log, if you don't init, it will use default logger setting | |||
func InitDefaultLogger(cfg *LogConfig) { | |||
var writers []zapcore.WriteSyncer | |||
if cfg.IsStdOut || (!cfg.IsStdOut && !cfg.IsFileOut) { | |||
writers = append(writers, os.Stdout) | |||
} | |||
if cfg.IsFileOut { | |||
writers = append(writers, NewRollingFile(cfg.FileDir, cfg.FileName, cfg.FileMaxSize, cfg.FileMaxAge)) | |||
} | |||
lg, lv := newZapLogger(setLogLevel(cfg.Level), setLogLevel(cfg.StacktraceLevel), cfg.Encoding, cfg.TimeFormat, cfg.Skip, zapcore.NewMultiWriteSyncer(writers...)) | |||
zap.RedirectStdLog(lg) | |||
if cfg.AppName != "" { | |||
lg = lg.With(zap.String("app", cfg.AppName)) // 加上应用名称 | |||
} | |||
l = &LogX{logger: lg, atomLevel: lv} | |||
} | |||
// create a new logger | |||
func NewLogger(cfg *LogConfig) *LogX { | |||
var writers []zapcore.WriteSyncer | |||
if cfg.IsStdOut || (!cfg.IsStdOut && !cfg.IsFileOut) { | |||
writers = append(writers, os.Stdout) | |||
} | |||
if cfg.IsFileOut { | |||
writers = append(writers, NewRollingFile(cfg.FileDir, cfg.FileName, cfg.FileMaxSize, cfg.FileMaxAge)) | |||
} | |||
lg, lv := newZapLogger(setLogLevel(cfg.Level), setLogLevel(cfg.StacktraceLevel), cfg.Encoding, cfg.TimeFormat, cfg.Skip, zapcore.NewMultiWriteSyncer(writers...)) | |||
zap.RedirectStdLog(lg) | |||
if cfg.AppName != "" { | |||
lg = lg.With(zap.String("app", cfg.AppName)) // 加上应用名称 | |||
} | |||
return &LogX{logger: lg, atomLevel: lv} | |||
} | |||
// create a new zaplog logger | |||
func newZapLogger(level, stacktrace zapcore.Level, encoding, timeType string, skip int, output zapcore.WriteSyncer) (*zap.Logger, *zap.AtomicLevel) { | |||
encCfg := zapcore.EncoderConfig{ | |||
TimeKey: "T", | |||
LevelKey: "L", | |||
NameKey: "N", | |||
CallerKey: "C", | |||
MessageKey: "M", | |||
StacktraceKey: "S", | |||
LineEnding: zapcore.DefaultLineEnding, | |||
EncodeCaller: zapcore.ShortCallerEncoder, | |||
EncodeDuration: zapcore.NanosDurationEncoder, | |||
EncodeLevel: zapcore.LowercaseLevelEncoder, | |||
} | |||
setTimeFormat(timeType, &encCfg) // set time type | |||
atmLvl := zap.NewAtomicLevel() // set level | |||
atmLvl.SetLevel(level) | |||
encoder := zapcore.NewJSONEncoder(encCfg) // 确定encoder格式 | |||
if encoding == "console" { | |||
encoder = zapcore.NewConsoleEncoder(encCfg) | |||
} | |||
return zap.New(zapcore.NewCore(encoder, output, atmLvl), zap.AddCaller(), zap.AddStacktrace(stacktrace), zap.AddCallerSkip(skip)), &atmLvl | |||
} | |||
// set log level | |||
func setLogLevel(lvl string) zapcore.Level { | |||
switch strings.ToLower(lvl) { | |||
case "panic": | |||
return zapcore.PanicLevel | |||
case "fatal": | |||
return zapcore.FatalLevel | |||
case "error": | |||
return zapcore.ErrorLevel | |||
case "warn", "warning": | |||
return zapcore.WarnLevel | |||
case "info": | |||
return zapcore.InfoLevel | |||
default: | |||
return zapcore.DebugLevel | |||
} | |||
} | |||
// set time format | |||
func setTimeFormat(timeType string, z *zapcore.EncoderConfig) { | |||
switch strings.ToLower(timeType) { | |||
case "iso": // iso8601 standard | |||
z.EncodeTime = zapcore.ISO8601TimeEncoder | |||
case "sec": // only for unix second, without millisecond | |||
z.EncodeTime = func(t time.Time, enc zapcore.PrimitiveArrayEncoder) { | |||
enc.AppendInt64(t.Unix()) | |||
} | |||
case "second": // unix second, with millisecond | |||
z.EncodeTime = zapcore.EpochTimeEncoder | |||
case "milli", "millisecond": // millisecond | |||
z.EncodeTime = zapcore.EpochMillisTimeEncoder | |||
case "nano", "nanosecond": // nanosecond | |||
z.EncodeTime = zapcore.EpochNanosTimeEncoder | |||
default: // standard format | |||
z.EncodeTime = func(t time.Time, enc zapcore.PrimitiveArrayEncoder) { | |||
enc.AppendString(t.Format("2006-01-02 15:04:05.000")) | |||
} | |||
} | |||
} | |||
func GetLevel() string { | |||
switch l.atomLevel.Level() { | |||
case zapcore.PanicLevel: | |||
return "panic" | |||
case zapcore.FatalLevel: | |||
return "fatal" | |||
case zapcore.ErrorLevel: | |||
return "error" | |||
case zapcore.WarnLevel: | |||
return "warn" | |||
case zapcore.InfoLevel: | |||
return "info" | |||
default: | |||
return "debug" | |||
} | |||
} | |||
func SetLevel(lvl string) { | |||
l.atomLevel.SetLevel(setLogLevel(lvl)) | |||
} | |||
// temporary add call skip | |||
func AddCallerSkip(skip int) *LogX { | |||
l.logger.WithOptions(zap.AddCallerSkip(skip)) | |||
return l | |||
} | |||
// permanent add call skip | |||
func AddDepth(skip int) *LogX { | |||
l.logger = l.logger.WithOptions(zap.AddCallerSkip(skip)) | |||
return l | |||
} | |||
// permanent add options | |||
func AddOptions(opts ...zap.Option) *LogX { | |||
l.logger = l.logger.WithOptions(opts...) | |||
return l | |||
} | |||
func AddField(k string, v interface{}) { | |||
l.logger.With(zap.Any(k, v)) | |||
} | |||
func AddFields(fields map[string]interface{}) *LogX { | |||
for k, v := range fields { | |||
l.logger.With(zap.Any(k, v)) | |||
} | |||
return l | |||
} | |||
// Normal log | |||
func Debug(e interface{}, args ...interface{}) error { | |||
return l.Debug(e, args...) | |||
} | |||
func Info(e interface{}, args ...interface{}) error { | |||
return l.Info(e, args...) | |||
} | |||
func Warn(e interface{}, args ...interface{}) error { | |||
return l.Warn(e, args...) | |||
} | |||
func Error(e interface{}, args ...interface{}) error { | |||
return l.Error(e, args...) | |||
} | |||
func Panic(e interface{}, args ...interface{}) error { | |||
return l.Panic(e, args...) | |||
} | |||
func Fatal(e interface{}, args ...interface{}) error { | |||
return l.Fatal(e, args...) | |||
} | |||
// Format logs | |||
func Debugf(format string, args ...interface{}) error { | |||
return l.Debugf(format, args...) | |||
} | |||
func Infof(format string, args ...interface{}) error { | |||
return l.Infof(format, args...) | |||
} | |||
func Warnf(format string, args ...interface{}) error { | |||
return l.Warnf(format, args...) | |||
} | |||
func Errorf(format string, args ...interface{}) error { | |||
return l.Errorf(format, args...) | |||
} | |||
func Panicf(format string, args ...interface{}) error { | |||
return l.Panicf(format, args...) | |||
} | |||
func Fatalf(format string, args ...interface{}) error { | |||
return l.Fatalf(format, args...) | |||
} | |||
func formatFieldMap(m FieldMap) []Field { | |||
var res []Field | |||
for k, v := range m { | |||
res = append(res, zap.Any(k, v)) | |||
} | |||
return res | |||
} |
@@ -0,0 +1,105 @@ | |||
package logx | |||
import ( | |||
"bytes" | |||
"io" | |||
"os" | |||
"path/filepath" | |||
"time" | |||
"gopkg.in/natefinch/lumberjack.v2" | |||
) | |||
// output interface | |||
type WriteSyncer interface { | |||
io.Writer | |||
Sync() error | |||
} | |||
// split writer | |||
func NewRollingFile(dir, filename string, maxSize, MaxAge int) WriteSyncer { | |||
s, err := os.Stat(dir) | |||
if err != nil || !s.IsDir() { | |||
os.RemoveAll(dir) | |||
if err := os.MkdirAll(dir, 0766); err != nil { | |||
panic(err) | |||
} | |||
} | |||
return newLumberjackWriteSyncer(&lumberjack.Logger{ | |||
Filename: filepath.Join(dir, filename), | |||
MaxSize: maxSize, // megabytes, MB | |||
MaxAge: MaxAge, // days | |||
LocalTime: true, | |||
Compress: false, | |||
}) | |||
} | |||
type lumberjackWriteSyncer struct { | |||
*lumberjack.Logger | |||
buf *bytes.Buffer | |||
logChan chan []byte | |||
closeChan chan interface{} | |||
maxSize int | |||
} | |||
func newLumberjackWriteSyncer(l *lumberjack.Logger) *lumberjackWriteSyncer { | |||
ws := &lumberjackWriteSyncer{ | |||
Logger: l, | |||
buf: bytes.NewBuffer([]byte{}), | |||
logChan: make(chan []byte, 5000), | |||
closeChan: make(chan interface{}), | |||
maxSize: 1024, | |||
} | |||
go ws.run() | |||
return ws | |||
} | |||
func (l *lumberjackWriteSyncer) run() { | |||
ticker := time.NewTicker(1 * time.Second) | |||
for { | |||
select { | |||
case <-ticker.C: | |||
if l.buf.Len() > 0 { | |||
l.sync() | |||
} | |||
case bs := <-l.logChan: | |||
_, err := l.buf.Write(bs) | |||
if err != nil { | |||
continue | |||
} | |||
if l.buf.Len() > l.maxSize { | |||
l.sync() | |||
} | |||
case <-l.closeChan: | |||
l.sync() | |||
return | |||
} | |||
} | |||
} | |||
func (l *lumberjackWriteSyncer) Stop() { | |||
close(l.closeChan) | |||
} | |||
func (l *lumberjackWriteSyncer) Write(bs []byte) (int, error) { | |||
b := make([]byte, len(bs)) | |||
for i, c := range bs { | |||
b[i] = c | |||
} | |||
l.logChan <- b | |||
return 0, nil | |||
} | |||
func (l *lumberjackWriteSyncer) Sync() error { | |||
return nil | |||
} | |||
func (l *lumberjackWriteSyncer) sync() error { | |||
defer l.buf.Reset() | |||
_, err := l.Logger.Write(l.buf.Bytes()) | |||
if err != nil { | |||
return err | |||
} | |||
return nil | |||
} |
@@ -0,0 +1,192 @@ | |||
package logx | |||
import ( | |||
"errors" | |||
"fmt" | |||
"strconv" | |||
"go.uber.org/zap" | |||
) | |||
type LogX struct { | |||
logger *zap.Logger | |||
atomLevel *zap.AtomicLevel | |||
} | |||
type Field = zap.Field | |||
type FieldMap map[string]interface{} | |||
// 判断其他类型--start | |||
func getFields(msg string, format bool, args ...interface{}) (string, []Field) { | |||
var str []interface{} | |||
var fields []zap.Field | |||
if len(args) > 0 { | |||
for _, v := range args { | |||
if f, ok := v.(Field); ok { | |||
fields = append(fields, f) | |||
} else if f, ok := v.(FieldMap); ok { | |||
fields = append(fields, formatFieldMap(f)...) | |||
} else { | |||
str = append(str, AnyToString(v)) | |||
} | |||
} | |||
if format { | |||
return fmt.Sprintf(msg, str...), fields | |||
} | |||
str = append([]interface{}{msg}, str...) | |||
return fmt.Sprintln(str...), fields | |||
} | |||
return msg, []Field{} | |||
} | |||
func (l *LogX) Debug(s interface{}, args ...interface{}) error { | |||
es, e := checkErr(s) | |||
if es != "" { | |||
msg, field := getFields(es, false, args...) | |||
l.logger.Debug(msg, field...) | |||
} | |||
return e | |||
} | |||
func (l *LogX) Info(s interface{}, args ...interface{}) error { | |||
es, e := checkErr(s) | |||
if es != "" { | |||
msg, field := getFields(es, false, args...) | |||
l.logger.Info(msg, field...) | |||
} | |||
return e | |||
} | |||
func (l *LogX) Warn(s interface{}, args ...interface{}) error { | |||
es, e := checkErr(s) | |||
if es != "" { | |||
msg, field := getFields(es, false, args...) | |||
l.logger.Warn(msg, field...) | |||
} | |||
return e | |||
} | |||
func (l *LogX) Error(s interface{}, args ...interface{}) error { | |||
es, e := checkErr(s) | |||
if es != "" { | |||
msg, field := getFields(es, false, args...) | |||
l.logger.Error(msg, field...) | |||
} | |||
return e | |||
} | |||
func (l *LogX) DPanic(s interface{}, args ...interface{}) error { | |||
es, e := checkErr(s) | |||
if es != "" { | |||
msg, field := getFields(es, false, args...) | |||
l.logger.DPanic(msg, field...) | |||
} | |||
return e | |||
} | |||
func (l *LogX) Panic(s interface{}, args ...interface{}) error { | |||
es, e := checkErr(s) | |||
if es != "" { | |||
msg, field := getFields(es, false, args...) | |||
l.logger.Panic(msg, field...) | |||
} | |||
return e | |||
} | |||
func (l *LogX) Fatal(s interface{}, args ...interface{}) error { | |||
es, e := checkErr(s) | |||
if es != "" { | |||
msg, field := getFields(es, false, args...) | |||
l.logger.Fatal(msg, field...) | |||
} | |||
return e | |||
} | |||
func checkErr(s interface{}) (string, error) { | |||
switch e := s.(type) { | |||
case error: | |||
return e.Error(), e | |||
case string: | |||
return e, errors.New(e) | |||
case []byte: | |||
return string(e), nil | |||
default: | |||
return "", nil | |||
} | |||
} | |||
func (l *LogX) LogError(err error) error { | |||
return l.Error(err.Error()) | |||
} | |||
func (l *LogX) Debugf(msg string, args ...interface{}) error { | |||
s, f := getFields(msg, true, args...) | |||
l.logger.Debug(s, f...) | |||
return errors.New(s) | |||
} | |||
func (l *LogX) Infof(msg string, args ...interface{}) error { | |||
s, f := getFields(msg, true, args...) | |||
l.logger.Info(s, f...) | |||
return errors.New(s) | |||
} | |||
func (l *LogX) Warnf(msg string, args ...interface{}) error { | |||
s, f := getFields(msg, true, args...) | |||
l.logger.Warn(s, f...) | |||
return errors.New(s) | |||
} | |||
func (l *LogX) Errorf(msg string, args ...interface{}) error { | |||
s, f := getFields(msg, true, args...) | |||
l.logger.Error(s, f...) | |||
return errors.New(s) | |||
} | |||
func (l *LogX) DPanicf(msg string, args ...interface{}) error { | |||
s, f := getFields(msg, true, args...) | |||
l.logger.DPanic(s, f...) | |||
return errors.New(s) | |||
} | |||
func (l *LogX) Panicf(msg string, args ...interface{}) error { | |||
s, f := getFields(msg, true, args...) | |||
l.logger.Panic(s, f...) | |||
return errors.New(s) | |||
} | |||
func (l *LogX) Fatalf(msg string, args ...interface{}) error { | |||
s, f := getFields(msg, true, args...) | |||
l.logger.Fatal(s, f...) | |||
return errors.New(s) | |||
} | |||
func AnyToString(raw interface{}) string { | |||
switch i := raw.(type) { | |||
case []byte: | |||
return string(i) | |||
case int: | |||
return strconv.FormatInt(int64(i), 10) | |||
case int64: | |||
return strconv.FormatInt(i, 10) | |||
case float32: | |||
return strconv.FormatFloat(float64(i), 'f', 2, 64) | |||
case float64: | |||
return strconv.FormatFloat(i, 'f', 2, 64) | |||
case uint: | |||
return strconv.FormatInt(int64(i), 10) | |||
case uint8: | |||
return strconv.FormatInt(int64(i), 10) | |||
case uint16: | |||
return strconv.FormatInt(int64(i), 10) | |||
case uint32: | |||
return strconv.FormatInt(int64(i), 10) | |||
case uint64: | |||
return strconv.FormatInt(int64(i), 10) | |||
case int8: | |||
return strconv.FormatInt(int64(i), 10) | |||
case int16: | |||
return strconv.FormatInt(int64(i), 10) | |||
case int32: | |||
return strconv.FormatInt(int64(i), 10) | |||
case string: | |||
return i | |||
case error: | |||
return i.Error() | |||
} | |||
return fmt.Sprintf("%#v", raw) | |||
} |
@@ -0,0 +1,12 @@ | |||
package utils | |||
import ( | |||
"crypto/md5" | |||
"encoding/hex" | |||
) | |||
func Md5(str string) string { | |||
h := md5.New() | |||
h.Write([]byte(str)) | |||
return hex.EncodeToString(h.Sum(nil)) | |||
} |
@@ -0,0 +1,46 @@ | |||
package utils | |||
import ( | |||
crand "crypto/rand" | |||
"fmt" | |||
"math/big" | |||
"math/rand" | |||
"time" | |||
) | |||
func RandString(l int, c ...string) string { | |||
var ( | |||
chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" | |||
str string | |||
num *big.Int | |||
) | |||
if len(c) > 0 { | |||
chars = c[0] | |||
} | |||
chrLen := int64(len(chars)) | |||
for len(str) < l { | |||
num, _ = crand.Int(crand.Reader, big.NewInt(chrLen)) | |||
str += string(chars[num.Int64()]) | |||
} | |||
return str | |||
} | |||
func RandNum() string { | |||
seed := time.Now().UnixNano() + rand.Int63() | |||
return fmt.Sprintf("%05v", rand.New(rand.NewSource(seed)).Int31n(1000000)) | |||
} | |||
//x的y次方 | |||
func RandPow(l int) string { | |||
var i = "1" | |||
for j := 0; j < l; j++ { | |||
i += "0" | |||
} | |||
k := StrToInt64(i) | |||
n := rand.New(rand.NewSource(time.Now().UnixNano())).Int63n(k) | |||
ls := "%0" + IntToStr(l) + "v" | |||
str := fmt.Sprintf(ls, n) | |||
//min := int(math.Pow10(l - 1)) | |||
//max := int(math.Pow10(l) - 1) | |||
return str | |||
} |
@@ -0,0 +1,29 @@ | |||
package utils | |||
import ( | |||
"errors" | |||
"fmt" | |||
) | |||
type Error string | |||
func (err Error) Error() string { return string(err) } | |||
var ErrNil = errors.New("redigo: nil returned") | |||
func Bytes(reply interface{}, err error) ([]byte, error) { | |||
if err != nil { | |||
return nil, err | |||
} | |||
switch reply := reply.(type) { | |||
case []byte: | |||
return reply, nil | |||
case string: | |||
return []byte(reply), nil | |||
case nil: | |||
return nil, ErrNil | |||
case Error: | |||
return nil, reply | |||
} | |||
return nil, fmt.Errorf("redigo: unexpected type for Bytes, got type %T", reply) | |||
} |
@@ -0,0 +1,23 @@ | |||
package utils | |||
import ( | |||
"encoding/json" | |||
) | |||
func Serialize(data interface{}) []byte { | |||
res, err := json.Marshal(data) | |||
if err != nil { | |||
return []byte{} | |||
} | |||
return res | |||
} | |||
func Unserialize(b []byte, dst interface{}) { | |||
if err := json.Unmarshal(b, dst); err != nil { | |||
dst = nil | |||
} | |||
} | |||
func SerializeStr(data interface{}, arg ...interface{}) string { | |||
return string(Serialize(data)) | |||
} |
@@ -0,0 +1,208 @@ | |||
package utils | |||
import ( | |||
"errors" | |||
"fmt" | |||
"strconv" | |||
"strings" | |||
"time" | |||
) | |||
func StrToTime(s string) (int64, error) { | |||
// delete all not int characters | |||
if s == "" { | |||
return time.Now().Unix(), nil | |||
} | |||
r := make([]rune, 14) | |||
l := 0 | |||
// 过滤除数字以外的字符 | |||
for _, v := range s { | |||
if '0' <= v && v <= '9' { | |||
r[l] = v | |||
l++ | |||
if l == 14 { | |||
break | |||
} | |||
} | |||
} | |||
for l < 14 { | |||
r[l] = '0' // 补0 | |||
l++ | |||
} | |||
t, err := time.Parse("20060102150405", string(r)) | |||
if err != nil { | |||
return 0, err | |||
} | |||
return t.Unix(), nil | |||
} | |||
func TimeToStr(unixSecTime interface{}, layout ...string) string { | |||
i := AnyToInt64(unixSecTime) | |||
if i == 0 { | |||
return "" | |||
} | |||
f := "2006-01-02 15:04:05" | |||
if len(layout) > 0 { | |||
f = layout[0] | |||
} | |||
return time.Unix(i, 0).Format(f) | |||
} | |||
func FormatNanoUnix() string { | |||
return strings.Replace(time.Now().Format("20060102150405.0000000"), ".", "", 1) | |||
} | |||
func TimeParse(format, src string) (time.Time, error) { | |||
return time.ParseInLocation(format, src, time.Local) | |||
} | |||
func TimeParseStd(src string) time.Time { | |||
t, _ := TimeParse("2006-01-02 15:04:05", src) | |||
return t | |||
} | |||
func TimeStdParseUnix(src string) int64 { | |||
t, err := TimeParse("2006-01-02 15:04:05", src) | |||
if err != nil { | |||
return 0 | |||
} | |||
return t.Unix() | |||
} | |||
// 获取一个当前时间 时间间隔 时间戳 | |||
func GetTimeInterval(unit string, amount int) (startTime, endTime int64) { | |||
t := time.Now() | |||
nowTime := t.Unix() | |||
tmpTime := int64(0) | |||
switch unit { | |||
case "years": | |||
tmpTime = time.Date(t.Year()+amount, t.Month(), t.Day(), t.Hour(), 0, 0, 0, t.Location()).Unix() | |||
case "months": | |||
tmpTime = time.Date(t.Year(), t.Month()+time.Month(amount), t.Day(), t.Hour(), 0, 0, 0, t.Location()).Unix() | |||
case "days": | |||
tmpTime = time.Date(t.Year(), t.Month(), t.Day()+amount, t.Hour(), 0, 0, 0, t.Location()).Unix() | |||
case "hours": | |||
tmpTime = time.Date(t.Year(), t.Month(), t.Day(), t.Hour()+amount, 0, 0, 0, t.Location()).Unix() | |||
} | |||
if amount > 0 { | |||
startTime = nowTime | |||
endTime = tmpTime | |||
} else { | |||
startTime = tmpTime | |||
endTime = nowTime | |||
} | |||
return | |||
} | |||
// 几天前 | |||
func TimeInterval(newTime int) string { | |||
now := time.Now().Unix() | |||
newTime64 := AnyToInt64(newTime) | |||
if newTime64 >= now { | |||
return "刚刚" | |||
} | |||
interval := now - newTime64 | |||
switch { | |||
case interval < 60: | |||
return AnyToString(interval) + "秒前" | |||
case interval < 60*60: | |||
return AnyToString(interval/60) + "分前" | |||
case interval < 60*60*24: | |||
return AnyToString(interval/60/60) + "小时前" | |||
case interval < 60*60*24*30: | |||
return AnyToString(interval/60/60/24) + "天前" | |||
case interval < 60*60*24*30*12: | |||
return AnyToString(interval/60/60/24/30) + "月前" | |||
default: | |||
return AnyToString(interval/60/60/24/30/12) + "年前" | |||
} | |||
} | |||
// 时分秒字符串转时间戳,传入示例:8:40 or 8:40:10 | |||
func HmsToUnix(str string) (int64, error) { | |||
t := time.Now() | |||
arr := strings.Split(str, ":") | |||
if len(arr) < 2 { | |||
return 0, errors.New("Time format error") | |||
} | |||
h, _ := strconv.Atoi(arr[0]) | |||
m, _ := strconv.Atoi(arr[1]) | |||
s := 0 | |||
if len(arr) == 3 { | |||
s, _ = strconv.Atoi(arr[3]) | |||
} | |||
formatted1 := fmt.Sprintf("%d%02d%02d%02d%02d%02d", t.Year(), t.Month(), t.Day(), h, m, s) | |||
res, err := time.ParseInLocation("20060102150405", formatted1, time.Local) | |||
if err != nil { | |||
return 0, err | |||
} else { | |||
return res.Unix(), nil | |||
} | |||
} | |||
// 获取特定时间范围 | |||
func GetTimeRange(s string) map[string]int64 { | |||
t := time.Now() | |||
var stime, etime time.Time | |||
switch s { | |||
case "today": | |||
stime = time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location()) | |||
etime = time.Date(t.Year(), t.Month(), t.Day()+1, 0, 0, 0, 0, t.Location()) | |||
case "yesterday": | |||
stime = time.Date(t.Year(), t.Month(), t.Day()-1, 0, 0, 0, 0, t.Location()) | |||
etime = time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location()) | |||
case "within_seven_days": | |||
// 前6天0点 | |||
stime = time.Date(t.Year(), t.Month(), t.Day()-6, 0, 0, 0, 0, t.Location()) | |||
// 明天 0点 | |||
etime = time.Date(t.Year(), t.Month(), t.Day()+1, 0, 0, 0, 0, t.Location()) | |||
case "current_month": | |||
stime = GetFirstDateOfMonth(t) | |||
etime = time.Now() | |||
case "last_month": | |||
etime = GetFirstDateOfMonth(t) | |||
monthTimes := TimeStdParseUnix(etime.Format("2006-01-02 15:04:05")) - 86400 | |||
times, _ := UnixToTime(Int64ToStr(monthTimes)) | |||
stime = GetFirstDateOfMonth(times) | |||
} | |||
return map[string]int64{ | |||
"start": stime.Unix(), | |||
"end": etime.Unix(), | |||
} | |||
} | |||
//时间戳转时间格式 | |||
func UnixToTime(e string) (datatime time.Time, err error) { | |||
data, err := strconv.ParseInt(e, 10, 64) | |||
datatime = time.Unix(data, 0) | |||
return | |||
} | |||
//获取传入的时间所在月份的第一天,即某月第一天的0点。如传入time.Now(), 返回当前月份的第一天0点时间。 | |||
func GetFirstDateOfMonth(d time.Time) time.Time { | |||
d = d.AddDate(0, 0, -d.Day()+1) | |||
return GetZeroTime(d) | |||
} | |||
//获取传入的时间所在月份的最后一天,即某月最后一天的0点。如传入time.Now(), 返回当前月份的最后一天0点时间。 | |||
func GetLastDateOfMonth(d time.Time) time.Time { | |||
return GetFirstDateOfMonth(d).AddDate(0, 1, -1) | |||
} | |||
//获取某一天的0点时间 | |||
func GetZeroTime(d time.Time) time.Time { | |||
return time.Date(d.Year(), d.Month(), d.Day(), 0, 0, 0, 0, d.Location()) | |||
} | |||
//获取当月某天的某个时间的时间 | |||
func GetDayToTime(day, timeStr string) string { | |||
if timeStr == "" { | |||
timeStr = "00:00:00" | |||
} | |||
year := time.Now().Year() | |||
month := time.Now().Format("01") | |||
times := fmt.Sprintf("%s-%s-%s %s", IntToStr(year), month, day, timeStr) | |||
return times | |||
} |
@@ -0,0 +1,7 @@ | |||
package model | |||
type Device struct { | |||
Type int32 // 设备类型,1:Android;2:IOS;3:Windows; 4:MacOS;5:Web | |||
Token string // token | |||
Expire int64 // 过期时间 | |||
} |
@@ -0,0 +1,39 @@ | |||
package model | |||
import ( | |||
"gim/pkg/pb" | |||
"time" | |||
) | |||
// User 账户 | |||
type User struct { | |||
Id int64 // 用户id | |||
PhoneNumber string // 手机号 | |||
Nickname string // 昵称 | |||
Sex int32 // 性别,1:男;2:女 | |||
AvatarUrl string // 用户头像 | |||
Extra string // 附加属性 | |||
CreateTime time.Time // 创建时间 | |||
UpdateTime time.Time // 更新时间 | |||
MasterId int64 // 站长id | |||
IsAutoAddedFriends int // 是否自动被添加好友 | |||
} | |||
func (u *User) ToProto() *pb.User { | |||
if u == nil { | |||
return nil | |||
} | |||
return &pb.User{ | |||
UserId: u.Id, | |||
Nickname: u.Nickname, | |||
Sex: u.Sex, | |||
AvatarUrl: u.AvatarUrl, | |||
Extra: u.Extra, | |||
CreateTime: u.CreateTime.Unix(), | |||
UpdateTime: u.UpdateTime.Unix(), | |||
MasterId: u.MasterId, | |||
IsAutoAddedFriends: int64(u.IsAutoAddedFriends), | |||
PhoneNumber: u.PhoneNumber, | |||
} | |||
} |
@@ -0,0 +1,74 @@ | |||
package repo | |||
import ( | |||
"encoding/json" | |||
"gim/internal/business/domain/user/model" | |||
"gim/pkg/db" | |||
"gim/pkg/gerrors" | |||
"gim/pkg/util" | |||
"strconv" | |||
"github.com/go-redis/redis" | |||
) | |||
const ( | |||
AuthKey = "auth:" | |||
) | |||
type authCache struct{} | |||
var AuthCache = new(authCache) | |||
func (*authCache) Get(userId, deviceId int64) (*model.Device, error) { | |||
bytes, err := db.RedisCli.HGet(AuthKey+strconv.FormatInt(userId, 10), strconv.FormatInt(deviceId, 10)).Bytes() | |||
if err != nil && err != redis.Nil { | |||
return nil, gerrors.WrapError(err) | |||
} | |||
if err == redis.Nil { | |||
return nil, nil | |||
} | |||
var device model.Device | |||
err = json.Unmarshal(bytes, &device) | |||
if err != nil { | |||
return nil, gerrors.WrapError(err) | |||
} | |||
return &device, nil | |||
} | |||
func (*authCache) Set(userId, deviceId int64, device model.Device) error { | |||
bytes, err := json.Marshal(device) | |||
if err != nil { | |||
return gerrors.WrapError(err) | |||
} | |||
_, err = db.RedisCli.HSet(AuthKey+strconv.FormatInt(userId, 10), strconv.FormatInt(deviceId, 10), bytes).Result() | |||
if err != nil { | |||
return gerrors.WrapError(err) | |||
} | |||
return nil | |||
} | |||
func (*authCache) GetAll(userId int64) (map[int64]model.Device, error) { | |||
result, err := db.RedisCli.HGetAll(AuthKey + strconv.FormatInt(userId, 10)).Result() | |||
if err != nil { | |||
return nil, gerrors.WrapError(err) | |||
} | |||
var devices = make(map[int64]model.Device, len(result)) | |||
for k, v := range result { | |||
deviceId, err := strconv.ParseInt(k, 10, 64) | |||
if err != nil { | |||
return nil, gerrors.WrapError(err) | |||
} | |||
var device model.Device | |||
err = json.Unmarshal(util.Str2bytes(v), &device) | |||
if err != nil { | |||
return nil, gerrors.WrapError(err) | |||
} | |||
devices[deviceId] = device | |||
} | |||
return devices, nil | |||
} |
@@ -0,0 +1,23 @@ | |||
package repo | |||
import ( | |||
"fmt" | |||
"gim/internal/business/domain/user/model" | |||
"testing" | |||
) | |||
func TestAuthCache_Get(t *testing.T) { | |||
fmt.Println(AuthCache.Get(1, 1)) | |||
} | |||
func TestAuthCache_Set(t *testing.T) { | |||
fmt.Println(AuthCache.Set(1, 1, model.Device{ | |||
Type: 1, | |||
Token: "111", | |||
Expire: 111, | |||
})) | |||
} | |||
func TestAuthCache_GetAll(t *testing.T) { | |||
fmt.Println(AuthCache.GetAll(1)) | |||
} |
@@ -0,0 +1,19 @@ | |||
package repo | |||
import "gim/internal/business/domain/user/model" | |||
type authRepo struct{} | |||
var AuthRepo = new(authRepo) | |||
func (*authRepo) Get(userId, deviceId int64) (*model.Device, error) { | |||
return AuthCache.Get(userId, deviceId) | |||
} | |||
func (*authRepo) Set(userId, deviceId int64, device model.Device) error { | |||
return AuthCache.Set(userId, deviceId, device) | |||
} | |||
func (*authRepo) GetAll(userId int64) (map[int64]model.Device, error) { | |||
return AuthCache.GetAll(userId) | |||
} |
@@ -0,0 +1,25 @@ | |||
package repo | |||
import ( | |||
"gim/internal/business/domain/user/model" | |||
"gim/pkg/db" | |||
"gim/pkg/gerrors" | |||
"github.com/jinzhu/gorm" | |||
) | |||
type imPackageDao struct{} | |||
var ImPackageDao = new(imPackageDao) | |||
// Get 获取套餐包详情 | |||
func (*imPackageDao) Get(id int64) (*model.ImPackage, error) { | |||
var imPackage = model.ImPackage{Id: id} | |||
err := db.DB.First(&imPackage).Error | |||
if err != nil && err != gorm.ErrRecordNotFound { | |||
return nil, gerrors.WrapError(err) | |||
} | |||
if err == gorm.ErrRecordNotFound { | |||
return nil, nil | |||
} | |||
return &imPackage, err | |||
} |
@@ -0,0 +1,18 @@ | |||
package repo | |||
import ( | |||
"gim/internal/business/domain/user/model" | |||
) | |||
type imPackageRepo struct{} | |||
var ImPackageRepo = new(imPackageRepo) | |||
// Get 获取单个套餐包 | |||
func (*imPackageRepo) Get(id int64) (*model.ImPackage, error) { | |||
imPackage, err := ImPackageDao.Get(id) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return imPackage, err | |||
} |
@@ -0,0 +1,51 @@ | |||
package repo | |||
import ( | |||
"gim/internal/business/domain/user/model" | |||
"gim/pkg/db" | |||
"gim/pkg/gerrors" | |||
"strconv" | |||
"time" | |||
"github.com/go-redis/redis" | |||
) | |||
const ( | |||
UserKey = "user:" | |||
UserExpire = 2 * time.Hour | |||
) | |||
type userCache struct{} | |||
var UserCache = new(userCache) | |||
// Get 获取用户缓存 | |||
func (c *userCache) Get(userId int64) (*model.User, error) { | |||
var user model.User | |||
err := db.RedisUtil.Get(UserKey+strconv.FormatInt(userId, 10), &user) | |||
if err != nil && err != redis.Nil { | |||
return nil, gerrors.WrapError(err) | |||
} | |||
if err == redis.Nil { | |||
return nil, nil | |||
} | |||
return &user, nil | |||
} | |||
// Set 设置用户缓存 | |||
func (c *userCache) Set(user model.User) error { | |||
err := db.RedisUtil.Set(UserKey+strconv.FormatInt(user.Id, 10), user, UserExpire) | |||
if err != nil { | |||
return gerrors.WrapError(err) | |||
} | |||
return nil | |||
} | |||
// Del 删除用户缓存 | |||
func (c *userCache) Del(userId int64) error { | |||
_, err := db.RedisCli.Del(UserKey + strconv.FormatInt(userId, 10)).Result() | |||
if err != nil { | |||
return gerrors.WrapError(err) | |||
} | |||
return nil | |||
} |
@@ -0,0 +1,81 @@ | |||
package repo | |||
import ( | |||
"gim/internal/business/domain/user/model" | |||
"gim/pkg/db" | |||
"gim/pkg/gerrors" | |||
"time" | |||
"github.com/jinzhu/gorm" | |||
) | |||
type userDao struct{} | |||
var UserDao = new(userDao) | |||
// Add 插入一条用户信息 | |||
func (*userDao) Add(user model.User) (int64, error) { | |||
user.CreateTime = time.Now() | |||
user.UpdateTime = time.Now() | |||
err := db.DB.Create(&user).Error | |||
if err != nil { | |||
return 0, gerrors.WrapError(err) | |||
} | |||
return user.Id, nil | |||
} | |||
// Get 获取用户信息 | |||
func (*userDao) Get(userId int64) (*model.User, error) { | |||
var user = model.User{Id: userId} | |||
err := db.DB.First(&user).Error | |||
if err != nil && err != gorm.ErrRecordNotFound { | |||
return nil, gerrors.WrapError(err) | |||
} | |||
if err == gorm.ErrRecordNotFound { | |||
return nil, nil | |||
} | |||
return &user, err | |||
} | |||
// Save 保存 | |||
func (*userDao) Save(user *model.User) error { | |||
err := db.DB.Save(user).Error | |||
if err != nil { | |||
return gerrors.WrapError(err) | |||
} | |||
return nil | |||
} | |||
// GetByPhoneNumber 根据手机号获取用户信息 | |||
func (*userDao) GetByPhoneNumber(phoneNumber string, masterId int64) (*model.User, error) { | |||
var user model.User | |||
err := db.DB.First(&user, "phone_number = ? and master_id = ?", phoneNumber, masterId).Error | |||
if err != nil && err != gorm.ErrRecordNotFound { | |||
return nil, gerrors.WrapError(err) | |||
} | |||
if err == gorm.ErrRecordNotFound { | |||
return nil, nil | |||
} | |||
return &user, err | |||
} | |||
// GetByIds 获取用户信息 | |||
func (*userDao) GetByIds(userIds []int64) ([]model.User, error) { | |||
var users []model.User | |||
err := db.DB.Find(&users, "id in (?)", userIds).Error | |||
if err != nil { | |||
return nil, gerrors.WrapError(err) | |||
} | |||
return users, err | |||
} | |||
// Search 查询用户,这里简单实现,生产环境建议使用ES | |||
func (*userDao) Search(key string, masterId int64) ([]model.User, error) { | |||
var users []model.User | |||
key = "%" + key + "%" | |||
err := db.DB.Where("phone_number like ? or nickname like ? ", key, key).Where("master_id = ?", masterId).Find(&users).Error | |||
if err != nil { | |||
return nil, gerrors.WrapError(err) | |||
} | |||
return users, nil | |||
} |
@@ -0,0 +1,44 @@ | |||
package repo | |||
import ( | |||
"fmt" | |||
"gim/internal/business/domain/user/model" | |||
"gim/pkg/db" | |||
"testing" | |||
) | |||
func init() { | |||
fmt.Println("init db") | |||
db.InitByTest() | |||
} | |||
func TestUserDao_Add(t *testing.T) { | |||
id, err := UserDao.Add(model.User{ | |||
PhoneNumber: "18829291351", | |||
Nickname: "Alber", | |||
Sex: 1, | |||
AvatarUrl: "AvatarUrl", | |||
Extra: "Extra", | |||
}) | |||
fmt.Printf("%+v\n %+v\n ", id, err) | |||
} | |||
func TestUserDao_Get(t *testing.T) { | |||
user, err := UserDao.Get(1) | |||
fmt.Printf("%+v\n %+v\n ", user, err) | |||
} | |||
func TestUserDao_GetByIds(t *testing.T) { | |||
users, err := UserDao.GetByIds([]int64{1, 2, 3}) | |||
fmt.Printf("%+v\n %+v\n ", users, err) | |||
} | |||
func TestUserDao_GetByPhoneNumber(t *testing.T) { | |||
user, err := UserDao.GetByPhoneNumber("18829291351", 123456) | |||
fmt.Printf("%+v\n %+v\n ", user, err) | |||
} | |||
func TestUserDao_Search(t *testing.T) { | |||
users, err := UserDao.Search("18229", 123456) | |||
fmt.Printf("%+v\n %+v\n ", users, err) | |||
} |
@@ -0,0 +1,64 @@ | |||
package repo | |||
import ( | |||
"gim/internal/business/domain/user/model" | |||
) | |||
type userRepo struct{} | |||
var UserRepo = new(userRepo) | |||
// Get 获取单个用户 | |||
func (*userRepo) Get(userId int64) (*model.User, error) { | |||
user, err := UserCache.Get(userId) | |||
if err != nil { | |||
return nil, err | |||
} | |||
if user != nil { | |||
return user, nil | |||
} | |||
user, err = UserDao.Get(userId) | |||
if err != nil { | |||
return nil, err | |||
} | |||
if user != nil { | |||
err = UserCache.Set(*user) | |||
if err != nil { | |||
return nil, err | |||
} | |||
} | |||
return user, err | |||
} | |||
func (*userRepo) GetByPhoneNumber(phoneNumber string, masterId int64) (*model.User, error) { | |||
return UserDao.GetByPhoneNumber(phoneNumber, masterId) | |||
} | |||
// GetByIds 获取多个用户 | |||
func (*userRepo) GetByIds(userIds []int64) ([]model.User, error) { | |||
return UserDao.GetByIds(userIds) | |||
} | |||
// Search 搜索用户 | |||
func (*userRepo) Search(key string, masterId int64) ([]model.User, error) { | |||
return UserDao.Search(key, masterId) | |||
} | |||
// Save 保存用户 | |||
func (*userRepo) Save(user *model.User) error { | |||
userId := user.Id | |||
err := UserDao.Save(user) | |||
if err != nil { | |||
return err | |||
} | |||
if userId != 0 { | |||
err = UserCache.Del(user.Id) | |||
if err != nil { | |||
return err | |||
} | |||
} | |||
return nil | |||
} |
@@ -0,0 +1,132 @@ | |||
package service | |||
import ( | |||
"context" | |||
"errors" | |||
"fmt" | |||
"gim/internal/business/comm/db" | |||
"gim/internal/business/comm/utils" | |||
"gim/internal/business/domain/user/model" | |||
"gim/internal/business/domain/user/repo" | |||
"gim/pkg/gerrors" | |||
"gim/pkg/pb" | |||
"gim/pkg/rpc" | |||
"gim/pkg/util" | |||
"strconv" | |||
"time" | |||
) | |||
type authService struct{} | |||
var AuthService = new(authService) | |||
// SignIn 登录 | |||
func (*authService) SignIn(ctx context.Context, phoneNumber, code string, masterId, deviceId int64, pushAlia, nickname, avatarUrl string) (bool, int64, string, int64, error) { | |||
if !Verify(phoneNumber, code) { | |||
return false, 0, "", 0, gerrors.ErrBadCode | |||
} | |||
user, err := repo.UserRepo.GetByPhoneNumber(phoneNumber, masterId) | |||
if err != nil { | |||
return false, 0, "", 0, err | |||
} | |||
var isNew = false | |||
if user == nil { | |||
user = &model.User{ | |||
PhoneNumber: phoneNumber, | |||
MasterId: masterId, | |||
Nickname: nickname, | |||
AvatarUrl: avatarUrl, | |||
CreateTime: time.Now(), | |||
UpdateTime: time.Now(), | |||
} | |||
err := repo.UserRepo.Save(user) | |||
if err != nil { | |||
return false, 0, "", 0, err | |||
} | |||
isNew = true | |||
} | |||
resp, err := rpc.GetLogicIntClient().GetDevice(ctx, &pb.GetDeviceReq{DeviceId: deviceId}) | |||
if err != nil { | |||
return false, 0, "", 0, err | |||
} | |||
// 方便测试 | |||
//token := "0" | |||
token := util.RandString(40) | |||
err = repo.AuthRepo.Set(user.Id, resp.Device.DeviceId, model.Device{ | |||
Type: resp.Device.Type, | |||
Token: token, | |||
Expire: time.Now().AddDate(0, 3, 0).Unix(), | |||
}) | |||
if err != nil { | |||
return false, 0, "", 0, err | |||
} | |||
if pushAlia != "" { | |||
userPushForJg, err := db.DbUserPushForJg.UserPushForJgGetOne(strconv.FormatInt(user.Id, 10), masterId) | |||
if err != nil { | |||
return false, 0, "", 0, err | |||
} | |||
if userPushForJg == nil { | |||
save := db.DbUserPushForJg.UserPushForJgInsert(user.Id, masterId, pushAlia) | |||
if !save { | |||
return false, 0, "", 0, errors.New("插入user_push_for_jg记录失败") | |||
} | |||
} else { | |||
update := db.DbUserPushForJg.UserPushForJgUpdate(user.Id, masterId, pushAlia) | |||
if !update { | |||
return false, 0, "", 0, errors.New("修改user_push_for_jg记录失败") | |||
} | |||
} | |||
} | |||
return isNew, user.Id, token, masterId, nil | |||
} | |||
func Verify(phoneNumber, code string) bool { | |||
// 假装他成功了 | |||
return true | |||
} | |||
// Auth 验证用户是否登录 | |||
func (*authService) Auth(ctx context.Context, userId, deviceId int64, token string) error { | |||
device, err := repo.AuthRepo.Get(userId, deviceId) | |||
if err != nil { | |||
return err | |||
} | |||
if device == nil { | |||
return gerrors.ErrUnauthorized | |||
} | |||
if device.Expire < time.Now().Unix() { | |||
return gerrors.ErrUnauthorized | |||
} | |||
if device.Token != token { | |||
return gerrors.ErrUnauthorized | |||
} | |||
return nil | |||
} | |||
// AuthMaster 验证站长权限 | |||
func (*authService) AuthMaster(ctx context.Context, masterId string) error { | |||
master, err := repo.MasterRepo.Get(utils.StrToInt64(masterId)) | |||
fmt.Println(">>>>>>>>>>>>>>AuthMaster>>>>>>>>>>>>>>>", master, err) | |||
if err != nil { | |||
return err | |||
} | |||
if master == nil { | |||
return gerrors.ErrMasterUnauthorized | |||
} | |||
packageExpireTime, _ := utils.StrToTime(master.PackageExpireTime) | |||
fmt.Println(">>>>>>>>>>>>>>packageExpireTime>>>>>>>>>>>>>>>", packageExpireTime, time.Now().Unix()) | |||
if packageExpireTime < time.Now().Unix() { | |||
return gerrors.ErrMasterUnauthorized | |||
} | |||
return nil | |||
} |
@@ -0,0 +1,12 @@ | |||
package service | |||
import ( | |||
"testing" | |||
) | |||
func TestAuthService_SignIn(t *testing.T) { | |||
} | |||
func TestAuthService_Auth(t *testing.T) { | |||
} |
@@ -0,0 +1,32 @@ | |||
package connect | |||
import ( | |||
"context" | |||
"gim/pkg/grpclib" | |||
"gim/pkg/logger" | |||
"gim/pkg/pb" | |||
"go.uber.org/zap" | |||
) | |||
type ConnIntServer struct{} | |||
// DeliverMessage 投递消息 | |||
func (s *ConnIntServer) DeliverMessage(ctx context.Context, req *pb.DeliverMessageReq) (*pb.Empty, error) { | |||
resp := &pb.Empty{} | |||
// 获取设备对应的TCP连接 | |||
conn := GetConn(req.DeviceId) | |||
if conn == nil { | |||
logger.Logger.Warn("GetConn warn", zap.Int64("device_id", req.DeviceId)) | |||
return resp, nil | |||
} | |||
if conn.DeviceId != req.DeviceId { | |||
logger.Logger.Warn("GetConn warn", zap.Int64("device_id", req.DeviceId)) | |||
return resp, nil | |||
} | |||
conn.Send(pb.PackageType_PT_MESSAGE, grpclib.GetCtxRequestId(ctx), req.MessageSend, nil) | |||
return resp, nil | |||
} |
@@ -0,0 +1,261 @@ | |||
package connect | |||
import ( | |||
"container/list" | |||
"context" | |||
"gim/config" | |||
"gim/pkg/grpclib" | |||
"gim/pkg/logger" | |||
"gim/pkg/pb" | |||
"gim/pkg/rpc" | |||
"sync" | |||
"time" | |||
"go.uber.org/zap" | |||
"google.golang.org/grpc/status" | |||
"google.golang.org/protobuf/proto" | |||
"github.com/alberliu/gn" | |||
"github.com/gorilla/websocket" | |||
) | |||
const ( | |||
CoonTypeTCP int8 = 1 // tcp连接 | |||
ConnTypeWS int8 = 2 // websocket连接 | |||
) | |||
type Conn struct { | |||
CoonType int8 // 连接类型 | |||
TCP *gn.Conn // tcp连接 | |||
WSMutex sync.Mutex // WS写锁 | |||
WS *websocket.Conn // websocket连接 | |||
UserId int64 // 用户ID | |||
DeviceId int64 // 设备ID | |||
RoomId int64 // 订阅的房间ID | |||
Element *list.Element // 链表节点 | |||
} | |||
// Write 写入数据 | |||
func (c *Conn) Write(bytes []byte) error { | |||
if c.CoonType == CoonTypeTCP { | |||
return c.TCP.WriteWithEncoder(bytes) | |||
} else if c.CoonType == ConnTypeWS { | |||
return c.WriteToWS(bytes) | |||
} | |||
logger.Logger.Error("unknown conn type", zap.Any("conn", c)) | |||
return nil | |||
} | |||
// WriteToWS 消息写入WebSocket | |||
func (c *Conn) WriteToWS(bytes []byte) error { | |||
c.WSMutex.Lock() | |||
defer c.WSMutex.Unlock() | |||
err := c.WS.SetWriteDeadline(time.Now().Add(10 * time.Millisecond)) | |||
if err != nil { | |||
return err | |||
} | |||
return c.WS.WriteMessage(websocket.BinaryMessage, bytes) | |||
} | |||
// Close 关闭 | |||
func (c *Conn) Close() error { | |||
// 取消设备和连接的对应关系 | |||
if c.DeviceId != 0 { | |||
DeleteConn(c.DeviceId) | |||
} | |||
// 取消订阅,需要异步出去,防止重复加锁造成死锁 | |||
go func() { | |||
SubscribedRoom(c, 0) | |||
}() | |||
if c.DeviceId != 0 { | |||
_, _ = rpc.GetLogicIntClient().Offline(context.TODO(), &pb.OfflineReq{ | |||
UserId: c.UserId, | |||
DeviceId: c.DeviceId, | |||
ClientAddr: c.GetAddr(), | |||
}) | |||
} | |||
if c.CoonType == CoonTypeTCP { | |||
c.TCP.Close() | |||
} else if c.CoonType == ConnTypeWS { | |||
return c.WS.Close() | |||
} | |||
return nil | |||
} | |||
func (c *Conn) GetAddr() string { | |||
if c.CoonType == CoonTypeTCP { | |||
return c.TCP.GetAddr() | |||
} else if c.CoonType == ConnTypeWS { | |||
return c.WS.RemoteAddr().String() | |||
} | |||
return "" | |||
} | |||
// HandleMessage 消息处理 | |||
func (c *Conn) HandleMessage(bytes []byte) { | |||
var input = new(pb.Input) | |||
err := proto.Unmarshal(bytes, input) | |||
if err != nil { | |||
logger.Logger.Error("unmarshal error", zap.Error(err)) | |||
return | |||
} | |||
logger.Logger.Debug("HandleMessage", zap.Any("input", input)) | |||
// 对未登录的用户进行拦截 | |||
if input.Type != pb.PackageType_PT_SIGN_IN && c.UserId == 0 { | |||
// 应该告诉用户没有登录 | |||
return | |||
} | |||
switch input.Type { | |||
case pb.PackageType_PT_SIGN_IN: | |||
c.SignIn(input) | |||
case pb.PackageType_PT_SYNC: | |||
c.Sync(input) | |||
case pb.PackageType_PT_HEARTBEAT: | |||
c.Heartbeat(input) | |||
case pb.PackageType_PT_MESSAGE: | |||
c.MessageACK(input) | |||
case pb.PackageType_PT_SUBSCRIBE_ROOM: | |||
c.SubscribedRoom(input) | |||
default: | |||
logger.Logger.Error("handler switch other") | |||
} | |||
} | |||
// Send 下发消息 | |||
func (c *Conn) Send(pt pb.PackageType, requestId int64, message proto.Message, err error) { | |||
var output = pb.Output{ | |||
Type: pt, | |||
RequestId: requestId, | |||
} | |||
if err != nil { | |||
status, _ := status.FromError(err) | |||
output.Code = int32(status.Code()) | |||
output.Message = status.Message() | |||
} | |||
if message != nil { | |||
msgBytes, err := proto.Marshal(message) | |||
if err != nil { | |||
logger.Sugar.Error(err) | |||
return | |||
} | |||
output.Data = msgBytes | |||
} | |||
outputBytes, err := proto.Marshal(&output) | |||
if err != nil { | |||
logger.Sugar.Error(err) | |||
return | |||
} | |||
err = c.Write(outputBytes) | |||
if err != nil { | |||
logger.Sugar.Error(err) | |||
c.Close() | |||
return | |||
} | |||
} | |||
// SignIn 登录 | |||
func (c *Conn) SignIn(input *pb.Input) { | |||
var signIn pb.SignInInput | |||
err := proto.Unmarshal(input.Data, &signIn) | |||
if err != nil { | |||
logger.Sugar.Error(err) | |||
return | |||
} | |||
_, err = rpc.GetLogicIntClient().ConnSignIn(grpclib.ContextWithRequestId(context.TODO(), input.RequestId), &pb.ConnSignInReq{ | |||
UserId: signIn.UserId, | |||
DeviceId: signIn.DeviceId, | |||
Token: signIn.Token, | |||
ConnAddr: config.LocalAddr, | |||
ClientAddr: c.GetAddr(), | |||
}) | |||
c.Send(pb.PackageType_PT_SIGN_IN, input.RequestId, nil, err) | |||
if err != nil { | |||
return | |||
} | |||
c.UserId = signIn.UserId | |||
c.DeviceId = signIn.DeviceId | |||
SetConn(signIn.DeviceId, c) | |||
} | |||
// Sync 消息同步 | |||
func (c *Conn) Sync(input *pb.Input) { | |||
var sync pb.SyncInput | |||
err := proto.Unmarshal(input.Data, &sync) | |||
if err != nil { | |||
logger.Sugar.Error(err) | |||
return | |||
} | |||
resp, err := rpc.GetLogicIntClient().Sync(grpclib.ContextWithRequestId(context.TODO(), input.RequestId), &pb.SyncReq{ | |||
UserId: c.UserId, | |||
DeviceId: c.DeviceId, | |||
Seq: sync.Seq, | |||
}) | |||
var message proto.Message | |||
if err == nil { | |||
message = &pb.SyncOutput{Messages: resp.Messages, HasMore: resp.HasMore} | |||
} | |||
c.Send(pb.PackageType_PT_SYNC, input.RequestId, message, err) | |||
} | |||
// Heartbeat 心跳 | |||
func (c *Conn) Heartbeat(input *pb.Input) { | |||
c.Send(pb.PackageType_PT_HEARTBEAT, input.RequestId, nil, nil) | |||
logger.Sugar.Infow("heartbeat", "device_id", c.DeviceId, "user_id", c.UserId) | |||
} | |||
// MessageACK 消息收到回执 | |||
func (c *Conn) MessageACK(input *pb.Input) { | |||
var messageACK pb.MessageACK | |||
err := proto.Unmarshal(input.Data, &messageACK) | |||
if err != nil { | |||
logger.Sugar.Error(err) | |||
return | |||
} | |||
_, _ = rpc.GetLogicIntClient().MessageACK(grpclib.ContextWithRequestId(context.TODO(), input.RequestId), &pb.MessageACKReq{ | |||
UserId: c.UserId, | |||
DeviceId: c.DeviceId, | |||
DeviceAck: messageACK.DeviceAck, | |||
ReceiveTime: messageACK.ReceiveTime, | |||
}) | |||
} | |||
// SubscribedRoom 订阅房间 | |||
func (c *Conn) SubscribedRoom(input *pb.Input) { | |||
var subscribeRoom pb.SubscribeRoomInput | |||
err := proto.Unmarshal(input.Data, &subscribeRoom) | |||
if err != nil { | |||
logger.Sugar.Error(err) | |||
return | |||
} | |||
SubscribedRoom(c, subscribeRoom.RoomId) | |||
c.Send(pb.PackageType_PT_SUBSCRIBE_ROOM, input.RequestId, nil, nil) | |||
_, err = rpc.GetLogicIntClient().SubscribeRoom(context.TODO(), &pb.SubscribeRoomReq{ | |||
UserId: c.UserId, | |||
DeviceId: c.DeviceId, | |||
RoomId: subscribeRoom.RoomId, | |||
Seq: subscribeRoom.Seq, | |||
ConnAddr: config.LocalAddr, | |||
}) | |||
if err != nil { | |||
logger.Logger.Error("SubscribedRoom error", zap.Error(err)) | |||
} | |||
} |
@@ -0,0 +1,36 @@ | |||
package connect | |||
import ( | |||
"gim/pkg/pb" | |||
"sync" | |||
) | |||
var ConnsManager = sync.Map{} | |||
// SetConn 存储 | |||
func SetConn(deviceId int64, conn *Conn) { | |||
ConnsManager.Store(deviceId, conn) | |||
} | |||
// GetConn 获取 | |||
func GetConn(deviceId int64) *Conn { | |||
value, ok := ConnsManager.Load(deviceId) | |||
if ok { | |||
return value.(*Conn) | |||
} | |||
return nil | |||
} | |||
// DeleteConn 删除 | |||
func DeleteConn(deviceId int64) { | |||
ConnsManager.Delete(deviceId) | |||
} | |||
// PushAll 全服推送 | |||
func PushAll(message *pb.MessageSend) { | |||
ConnsManager.Range(func(key, value interface{}) bool { | |||
conn := value.(*Conn) | |||
conn.Send(pb.PackageType_PT_MESSAGE, 0, message, nil) | |||
return true | |||
}) | |||
} |
@@ -0,0 +1,72 @@ | |||
package connect | |||
import ( | |||
"gim/config" | |||
"gim/pkg/db" | |||
"gim/pkg/logger" | |||
"gim/pkg/mq" | |||
"gim/pkg/pb" | |||
"time" | |||
"github.com/go-redis/redis" | |||
"go.uber.org/zap" | |||
"google.golang.org/protobuf/proto" | |||
) | |||
// StartSubscribe 启动MQ消息处理逻辑 | |||
func StartSubscribe() { | |||
pushRoomPriorityChannel := db.RedisCli.Subscribe(mq.PushRoomPriorityTopic).Channel() | |||
pushRoomChannel := db.RedisCli.Subscribe(mq.PushRoomTopic).Channel() | |||
for i := 0; i < config.PushRoomSubscribeNum; i++ { | |||
go handlePushRoomMsg(pushRoomPriorityChannel, pushRoomChannel) | |||
} | |||
pushAllChannel := db.RedisCli.Subscribe(mq.PushAllTopic).Channel() | |||
for i := 0; i < config.PushAllSubscribeNum; i++ { | |||
go handlePushAllMsg(pushAllChannel) | |||
} | |||
} | |||
func handlePushRoomMsg(priorityChannel, channel <-chan *redis.Message) { | |||
for { | |||
select { | |||
case msg := <-priorityChannel: | |||
handlePushRoom([]byte(msg.Payload)) | |||
default: | |||
select { | |||
case msg := <-channel: | |||
handlePushRoom([]byte(msg.Payload)) | |||
default: | |||
time.Sleep(100 * time.Millisecond) | |||
continue | |||
} | |||
} | |||
} | |||
} | |||
func handlePushAllMsg(channel <-chan *redis.Message) { | |||
for msg := range channel { | |||
handlePushAll([]byte(msg.Payload)) | |||
} | |||
} | |||
func handlePushRoom(bytes []byte) { | |||
var msg pb.PushRoomMsg | |||
err := proto.Unmarshal(bytes, &msg) | |||
if err != nil { | |||
logger.Logger.Error("handlePushRoom error", zap.Error(err)) | |||
return | |||
} | |||
PushRoom(msg.RoomId, msg.MessageSend) | |||
} | |||
func handlePushAll(bytes []byte) { | |||
var msg pb.PushAllMsg | |||
err := proto.Unmarshal(bytes, &msg) | |||
if err != nil { | |||
logger.Logger.Error("handlePushRoom error", zap.Error(err)) | |||
return | |||
} | |||
PushAll(msg.MessageSend) | |||
} |
@@ -0,0 +1,105 @@ | |||
package connect | |||
import ( | |||
"container/list" | |||
"gim/pkg/pb" | |||
"sync" | |||
) | |||
var RoomsManager sync.Map | |||
// SubscribedRoom 订阅房间 | |||
func SubscribedRoom(conn *Conn, roomId int64) { | |||
if roomId == conn.RoomId { | |||
return | |||
} | |||
oldRoomId := conn.RoomId | |||
// 取消订阅 | |||
if oldRoomId != 0 { | |||
value, ok := RoomsManager.Load(oldRoomId) | |||
if !ok { | |||
return | |||
} | |||
room := value.(*Room) | |||
room.Unsubscribe(conn) | |||
if room.Conns.Front() == nil { | |||
RoomsManager.Delete(oldRoomId) | |||
} | |||
return | |||
} | |||
// 订阅 | |||
if roomId != 0 { | |||
value, ok := RoomsManager.Load(roomId) | |||
var room *Room | |||
if !ok { | |||
room = NewRoom(roomId) | |||
RoomsManager.Store(roomId, room) | |||
} else { | |||
room = value.(*Room) | |||
} | |||
room.Subscribe(conn) | |||
return | |||
} | |||
} | |||
// PushRoom 房间消息推送 | |||
func PushRoom(roomId int64, message *pb.MessageSend) { | |||
value, ok := RoomsManager.Load(roomId) | |||
if !ok { | |||
return | |||
} | |||
value.(*Room).Push(message) | |||
} | |||
type Room struct { | |||
RoomId int64 // 房间ID | |||
Conns *list.List // 订阅房间消息的连接 | |||
lock sync.RWMutex | |||
} | |||
func NewRoom(roomId int64) *Room { | |||
return &Room{ | |||
RoomId: roomId, | |||
Conns: list.New(), | |||
} | |||
} | |||
// Subscribe 订阅房间 | |||
func (r *Room) Subscribe(conn *Conn) { | |||
r.lock.Lock() | |||
defer r.lock.Unlock() | |||
conn.Element = r.Conns.PushBack(conn) | |||
conn.RoomId = r.RoomId | |||
} | |||
// Unsubscribe 取消订阅 | |||
func (r *Room) Unsubscribe(conn *Conn) { | |||
r.lock.Lock() | |||
defer r.lock.Unlock() | |||
r.Conns.Remove(conn.Element) | |||
conn.Element = nil | |||
conn.RoomId = 0 | |||
} | |||
// Push 推送消息到房间 | |||
func (r *Room) Push(message *pb.MessageSend) { | |||
r.lock.RLock() | |||
defer r.lock.RUnlock() | |||
element := r.Conns.Front() | |||
for { | |||
conn := element.Value.(*Conn) | |||
conn.Send(pb.PackageType_PT_MESSAGE, 0, message, nil) | |||
element = element.Next() | |||
if element == nil { | |||
break | |||
} | |||
} | |||
} |
@@ -0,0 +1,73 @@ | |||
package connect | |||
import ( | |||
"context" | |||
"gim/pkg/logger" | |||
"gim/pkg/pb" | |||
"gim/pkg/rpc" | |||
"time" | |||
"go.uber.org/zap" | |||
"github.com/alberliu/gn" | |||
) | |||
var encoder = gn.NewHeaderLenEncoder(2, 1024) | |||
var server *gn.Server | |||
// StartTCPServer 启动TCP服务器 | |||
func StartTCPServer(addr string) { | |||
gn.SetLogger(logger.Sugar) | |||
var err error | |||
server, err = gn.NewServer(addr, &handler{}, | |||
gn.WithDecoder(gn.NewHeaderLenDecoder(2)), | |||
gn.WithEncoder(gn.NewHeaderLenEncoder(2, 1024)), | |||
gn.WithReadBufferLen(256), | |||
gn.WithTimeout(11*time.Minute), | |||
gn.WithAcceptGNum(10), | |||
gn.WithIOGNum(100)) | |||
if err != nil { | |||
logger.Sugar.Error(err) | |||
panic(err) | |||
} | |||
server.Run() | |||
} | |||
type handler struct{} | |||
func (*handler) OnConnect(c *gn.Conn) { | |||
// 初始化连接数据 | |||
conn := &Conn{ | |||
CoonType: CoonTypeTCP, | |||
TCP: c, | |||
} | |||
c.SetData(conn) | |||
logger.Logger.Debug("connect:", zap.Int32("fd", c.GetFd()), zap.String("addr", c.GetAddr())) | |||
} | |||
func (*handler) OnMessage(c *gn.Conn, bytes []byte) { | |||
conn := c.GetData().(*Conn) | |||
conn.HandleMessage(bytes) | |||
} | |||
func (*handler) OnClose(c *gn.Conn, err error) { | |||
conn, ok := c.GetData().(*Conn) | |||
if !ok || conn == nil { | |||
return | |||
} | |||
logger.Logger.Debug("close", zap.String("addr", c.GetAddr()), zap.Int64("user_id", conn.UserId), | |||
zap.Int64("device_id", conn.DeviceId), zap.Error(err)) | |||
DeleteConn(conn.DeviceId) | |||
if conn.UserId != 0 { | |||
_, _ = rpc.GetLogicIntClient().Offline(context.TODO(), &pb.OfflineReq{ | |||
UserId: conn.UserId, | |||
DeviceId: conn.DeviceId, | |||
ClientAddr: c.GetAddr(), | |||
}) | |||
} | |||
} |
@@ -0,0 +1,87 @@ | |||
package connect | |||
import ( | |||
"gim/pkg/logger" | |||
"gim/pkg/util" | |||
"io" | |||
"net/http" | |||
"strings" | |||
"time" | |||
"go.uber.org/zap" | |||
"github.com/gorilla/websocket" | |||
) | |||
var upgrader = websocket.Upgrader{ | |||
ReadBufferSize: 1024, | |||
WriteBufferSize: 65536, | |||
CheckOrigin: func(r *http.Request) bool { | |||
return true | |||
}, | |||
} | |||
// StartWSServer 启动WebSocket服务器 | |||
func StartWSServer(address string) { | |||
http.HandleFunc("/ws", wsHandler) | |||
logger.Logger.Info("websocket server start") | |||
err := http.ListenAndServe(address, nil) | |||
if err != nil { | |||
panic(err) | |||
} | |||
} | |||
func wsHandler(w http.ResponseWriter, r *http.Request) { | |||
wsConn, err := upgrader.Upgrade(w, r, nil) | |||
if err != nil { | |||
logger.Sugar.Error(err) | |||
return | |||
} | |||
conn := &Conn{ | |||
CoonType: ConnTypeWS, | |||
WS: wsConn, | |||
} | |||
DoConn(conn) | |||
} | |||
// DoConn 处理连接 | |||
func DoConn(conn *Conn) { | |||
defer util.RecoverPanic() | |||
for { | |||
err := conn.WS.SetReadDeadline(time.Now().Add(12 * time.Minute)) | |||
if err != nil { | |||
HandleReadErr(conn, err) | |||
return | |||
} | |||
_, data, err := conn.WS.ReadMessage() | |||
if err != nil { | |||
HandleReadErr(conn, err) | |||
return | |||
} | |||
conn.HandleMessage(data) | |||
} | |||
} | |||
// HandleReadErr 读取conn错误 | |||
func HandleReadErr(conn *Conn, err error) { | |||
logger.Logger.Debug("read tcp error:", zap.Int64("user_id", conn.UserId), | |||
zap.Int64("device_id", conn.DeviceId), zap.Error(err)) | |||
str := err.Error() | |||
// 服务器主动关闭连接 | |||
if strings.HasSuffix(str, "use of closed network connection") { | |||
return | |||
} | |||
conn.Close() | |||
// 客户端主动关闭连接或者异常程序退出 | |||
if err == io.EOF { | |||
return | |||
} | |||
// SetReadDeadline 之后,超时返回的错误 | |||
if strings.HasSuffix(str, "i/o timeout") { | |||
return | |||
} | |||
} |
@@ -0,0 +1,305 @@ | |||
package api | |||
import ( | |||
"context" | |||
"gim/internal/business/comm/utils" | |||
"gim/internal/logic/app" | |||
"gim/pkg/grpclib" | |||
"gim/pkg/pb" | |||
"google.golang.org/protobuf/proto" | |||
) | |||
type LogicExtServer struct{} | |||
func (s *LogicExtServer) SetGroupAddFriend(ctx context.Context, req *pb.SetGroupAddFriendReq) (*pb.Empty, error) { | |||
userId, _, err := grpclib.GetCtxData(ctx) | |||
if err != nil { | |||
return nil, err | |||
} | |||
err = app.GroupApp.SetGroupAddFriend(ctx, userId, req) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return &pb.Empty{}, nil | |||
} | |||
func (s *LogicExtServer) SetGroupRemoveBannedMembers(ctx context.Context, in *pb.SetGroupMemberRemoveBannedReq) (*pb.Empty, error) { | |||
userId, _, err := grpclib.GetCtxData(ctx) | |||
if err != nil { | |||
return nil, err | |||
} | |||
err = app.GroupApp.SetGroupMemberRemoveBanned(ctx, userId, in.GroupId, in.RemoveUserIds) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return &pb.Empty{}, nil | |||
} | |||
// SetGroupBannedMembers 设置禁言 | |||
func (s *LogicExtServer) SetGroupBannedMembers(ctx context.Context, in *pb.SetGroupMemberBannedReq) (*pb.SetGroupMemberBannedResp, error) { | |||
userId, _, err := grpclib.GetCtxData(ctx) | |||
if err != nil { | |||
return nil, err | |||
} | |||
var isAllMemberBanned = false | |||
if in.IsAllMemberBanned == pb.AllMemberBannedType_YES_All_Member_Banned { | |||
isAllMemberBanned = true | |||
} | |||
members, err := app.GroupApp.SetGroupMemberBanned(ctx, userId, in.GroupId, in.UserIds, isAllMemberBanned) | |||
return &pb.SetGroupMemberBannedResp{Members: members, IsAllMemberBanned: in.IsAllMemberBanned}, err | |||
} | |||
// GetGroupBannedMembers 获取群组禁言列表 | |||
func (s *LogicExtServer) GetGroupBannedMembers(ctx context.Context, in *pb.GetGroupBannedMembersReq) (*pb.GetGroupBannedMembersResp, error) { | |||
members, err := app.GroupApp.GetBannedMembers(ctx, in.GroupId) | |||
return &pb.GetGroupBannedMembersResp{Members: members}, err | |||
} | |||
func (s *LogicExtServer) SendRedPacket(ctx context.Context, in *pb.SendRedPacketReq) (*pb.SendRedPacketResp, error) { | |||
userId, deviceId, err := grpclib.GetCtxData(ctx) | |||
if err != nil { | |||
return nil, err | |||
} | |||
if in.MessageContentBack != "" { | |||
buf, err := proto.Marshal(&pb.Text{ | |||
Text: in.MessageContentBack, | |||
}) | |||
if err != nil { | |||
return nil, err | |||
} | |||
in.MessageContent = buf | |||
} | |||
sender := pb.Sender{ | |||
SenderType: pb.SenderType_ST_USER, | |||
SenderId: userId, | |||
DeviceId: deviceId, | |||
} | |||
seq, err := app.MessageApp.SendRedPackage(ctx, &sender, in) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return &pb.SendRedPacketResp{Seq: seq}, nil | |||
} | |||
func (s *LogicExtServer) RecallMessage(ctx context.Context, in *pb.RecallMessageReq) (*pb.RecallMessageResp, error) { | |||
userId, deviceId, err := grpclib.GetCtxData(ctx) | |||
if err != nil { | |||
return nil, err | |||
} | |||
if in.MessageContentBack != "" { | |||
buf, err := proto.Marshal(&pb.RECALL{RecallSeq: utils.StrToInt64(in.MessageContentBack)}) | |||
if err != nil { | |||
return nil, err | |||
} | |||
in.MessageContent = buf | |||
} | |||
sender := pb.Sender{ | |||
SenderType: pb.SenderType_ST_USER, | |||
SenderId: userId, | |||
DeviceId: deviceId, | |||
} | |||
utils.FilePutContents("RecallMessage", utils.SerializeStr(map[string]interface{}{ | |||
"args": in, | |||
})) | |||
seq, err := app.MessageApp.RecallMessage(ctx, &sender, in) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return &pb.RecallMessageResp{Seq: seq}, nil | |||
} | |||
// RegisterDevice 注册设备 | |||
func (*LogicExtServer) RegisterDevice(ctx context.Context, in *pb.RegisterDeviceReq) (*pb.RegisterDeviceResp, error) { | |||
deviceId, err := app.DeviceApp.Register(ctx, in) | |||
return &pb.RegisterDeviceResp{DeviceId: deviceId}, err | |||
} | |||
// SendMessage 发送消息 | |||
func (*LogicExtServer) SendMessage(ctx context.Context, in *pb.SendMessageReq) (*pb.SendMessageResp, error) { | |||
userId, deviceId, err := grpclib.GetCtxData(ctx) | |||
if err != nil { | |||
return nil, err | |||
} | |||
if in.MessageContentBack != "" { | |||
buf, err := proto.Marshal(&pb.Text{ | |||
Text: in.MessageContentBack, | |||
}) | |||
if err != nil { | |||
return nil, err | |||
} | |||
in.MessageContent = buf | |||
} | |||
sender := pb.Sender{ | |||
SenderType: pb.SenderType_ST_USER, | |||
SenderId: userId, | |||
DeviceId: deviceId, | |||
} | |||
seq, err := app.MessageApp.SendMessage(ctx, &sender, in) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return &pb.SendMessageResp{Seq: seq}, nil | |||
} | |||
// PushRoom 推送房间 | |||
func (s *LogicExtServer) PushRoom(ctx context.Context, req *pb.PushRoomReq) (*pb.Empty, error) { | |||
userId, deviceId, err := grpclib.GetCtxData(ctx) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return &pb.Empty{}, app.RoomApp.Push(ctx, &pb.Sender{ | |||
SenderType: pb.SenderType_ST_USER, | |||
SenderId: userId, | |||
DeviceId: deviceId, | |||
}, req) | |||
} | |||
func (s *LogicExtServer) AddFriend(ctx context.Context, in *pb.AddFriendReq) (*pb.Empty, error) { | |||
userId, _, err := grpclib.GetCtxData(ctx) | |||
if err != nil { | |||
return nil, err | |||
} | |||
err = app.FriendApp.AddFriend(ctx, userId, in.FriendId, in.Remarks, in.Description) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return &pb.Empty{}, nil | |||
} | |||
func (s *LogicExtServer) AgreeAddFriend(ctx context.Context, in *pb.AgreeAddFriendReq) (*pb.Empty, error) { | |||
userId, _, err := grpclib.GetCtxData(ctx) | |||
if err != nil { | |||
return nil, err | |||
} | |||
err = app.FriendApp.AgreeAddFriend(ctx, userId, in.UserId, in.Remarks) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return &pb.Empty{}, nil | |||
} | |||
func (s *LogicExtServer) DeleteFriend(ctx context.Context, in *pb.DeleteFriendReq) (*pb.Empty, error) { | |||
userId, _, err := grpclib.GetCtxData(ctx) | |||
if err != nil { | |||
return nil, err | |||
} | |||
err = app.FriendApp.DeleteFriend(ctx, userId, in.UserId) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return &pb.Empty{}, nil | |||
} | |||
func (s *LogicExtServer) SetFriend(ctx context.Context, req *pb.SetFriendReq) (*pb.SetFriendResp, error) { | |||
userId, _, err := grpclib.GetCtxData(ctx) | |||
if err != nil { | |||
return nil, err | |||
} | |||
app.FriendApp.SetFriend(ctx, userId, req) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return &pb.SetFriendResp{}, nil | |||
} | |||
func (s *LogicExtServer) GetFriends(ctx context.Context, in *pb.Empty) (*pb.GetFriendsResp, error) { | |||
userId, _, err := grpclib.GetCtxData(ctx) | |||
if err != nil { | |||
return nil, err | |||
} | |||
friends, err := app.FriendApp.List(ctx, userId) | |||
return &pb.GetFriendsResp{Friends: friends}, err | |||
} | |||
// CreateGroup 创建群组 | |||
func (*LogicExtServer) CreateGroup(ctx context.Context, in *pb.CreateGroupReq) (*pb.CreateGroupResp, error) { | |||
userId, _, err := grpclib.GetCtxData(ctx) | |||
if err != nil { | |||
return nil, err | |||
} | |||
groupId, err := app.GroupApp.CreateGroup(ctx, userId, in) | |||
return &pb.CreateGroupResp{GroupId: groupId}, err | |||
} | |||
// UpdateGroup 更新群组 | |||
func (*LogicExtServer) UpdateGroup(ctx context.Context, in *pb.UpdateGroupReq) (*pb.Empty, error) { | |||
userId, _, err := grpclib.GetCtxData(ctx) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return &pb.Empty{}, app.GroupApp.Update(ctx, userId, in) | |||
} | |||
// GetGroup 获取群组信息 | |||
func (*LogicExtServer) GetGroup(ctx context.Context, in *pb.GetGroupReq) (*pb.GetGroupResp, error) { | |||
group, memberType, userStatusType, err := app.GroupApp.GetGroup(ctx, in.GroupId) | |||
return &pb.GetGroupResp{Group: group, MemberType: memberType, GroupUserStatusType: userStatusType}, err | |||
} | |||
// GetGroups 获取用户加入的所有群组 | |||
func (*LogicExtServer) GetGroups(ctx context.Context, in *pb.Empty) (*pb.GetGroupsResp, error) { | |||
userId, _, err := grpclib.GetCtxData(ctx) | |||
if err != nil { | |||
return nil, err | |||
} | |||
groups, err := app.GroupApp.GetUserGroups(ctx, userId) | |||
return &pb.GetGroupsResp{Groups: groups}, err | |||
} | |||
func (s *LogicExtServer) AddGroupMembers(ctx context.Context, in *pb.AddGroupMembersReq) (*pb.AddGroupMembersResp, error) { | |||
userId, _, err := grpclib.GetCtxData(ctx) | |||
if err != nil { | |||
return nil, err | |||
} | |||
masterId, err := grpclib.GetCtxMasterId(ctx) | |||
if err != nil { | |||
return nil, err | |||
} | |||
userIds, err := app.GroupApp.AddMembers(ctx, userId, in.GroupId, in.UserIds, masterId) | |||
return &pb.AddGroupMembersResp{UserIds: userIds}, err | |||
} | |||
// UpdateGroupMember 更新群组成员信息 | |||
func (*LogicExtServer) UpdateGroupMember(ctx context.Context, in *pb.UpdateGroupMemberReq) (*pb.Empty, error) { | |||
userId, _, err := grpclib.GetCtxData(ctx) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return &pb.Empty{}, app.GroupApp.UpdateMember(ctx, in, userId) | |||
} | |||
// DeleteGroupMember 删除群组成员 | |||
func (*LogicExtServer) DeleteGroupMember(ctx context.Context, in *pb.DeleteGroupMemberReq) (*pb.Empty, error) { | |||
userId, _, err := grpclib.GetCtxData(ctx) | |||
if err != nil { | |||
return nil, err | |||
} | |||
err = app.GroupApp.DeleteMember(ctx, in.GroupId, in.UserId, userId) | |||
return &pb.Empty{}, err | |||
} | |||
// GetGroupMembers 获取群组成员信息 | |||
func (s *LogicExtServer) GetGroupMembers(ctx context.Context, in *pb.GetGroupMembersReq) (*pb.GetGroupMembersResp, error) { | |||
members, err := app.GroupApp.GetMembers(ctx, in.GroupId, in.Limit) | |||
utils.FilePutContents("GetGroupMembers", utils.SerializeStr(map[string]interface{}{ | |||
"req": map[string]interface{}{ | |||
"group_id": in.GroupId, | |||
"limit": in.Limit, | |||
}, | |||
"resp": members, | |||
})) | |||
return &pb.GetGroupMembersResp{Members: members}, err | |||
} |
@@ -0,0 +1,257 @@ | |||
package api | |||
import ( | |||
"context" | |||
"fmt" | |||
"gim/config" | |||
"gim/internal/logic/domain/message/model" | |||
"gim/pkg/db" | |||
"gim/pkg/pb" | |||
"gim/pkg/util" | |||
"github.com/jinzhu/gorm" | |||
"strconv" | |||
"testing" | |||
"time" | |||
"google.golang.org/grpc" | |||
"google.golang.org/grpc/metadata" | |||
"google.golang.org/protobuf/proto" | |||
) | |||
func getLogicExtClient() pb.LogicExtClient { | |||
conn, err := grpc.Dial("im-rpc-logic.izhyin.com:8003", grpc.WithInsecure()) | |||
if err != nil { | |||
fmt.Println(err) | |||
return nil | |||
} | |||
return pb.NewLogicExtClient(conn) | |||
} | |||
// deprecated: | |||
func getCtx() context.Context { | |||
token := "ICXKTELAGMMJYXITSIOUNXFHYMTCWJHMJCIRZLPX" | |||
return metadata.NewOutgoingContext(context.TODO(), metadata.Pairs( | |||
"user_id", "2", | |||
"device_id", "5", | |||
"token", token, | |||
"master_id", "123456", | |||
"request_id", strconv.FormatInt(time.Now().UnixNano(), 10))) | |||
} | |||
func TestLogicExtServer_RegisterDevice(t *testing.T) { | |||
resp, err := getLogicExtClient().RegisterDevice(context.TODO(), | |||
&pb.RegisterDeviceReq{ | |||
Type: 1, | |||
Brand: "huawei", | |||
Model: "huawei P30", | |||
SystemVersion: "1.0.0", | |||
SdkVersion: "1.0.0", | |||
}) | |||
if err != nil { | |||
fmt.Println(err) | |||
return | |||
} | |||
fmt.Printf("%+v\n", resp) | |||
} | |||
func TestLogicExtServer_SendMessage(t *testing.T) { | |||
buf, err := proto.Marshal(&pb.Text{ | |||
Text: "hello world!", | |||
}) | |||
if err != nil { | |||
fmt.Println(err) | |||
return | |||
} | |||
var one = string(buf) | |||
fmt.Printf(one) | |||
resp, err := getLogicExtClient().SendMessage(getCtx(), | |||
&pb.SendMessageReq{ | |||
ReceiverType: pb.ReceiverType_RT_USER, | |||
ReceiverId: 21, | |||
ToUserIds: nil, | |||
MessageType: pb.MessageType_MT_TEXT, | |||
MessageContent: buf, | |||
IsPersist: true, | |||
SendTime: util.UnixMilliTime(time.Now()), | |||
}) | |||
if err != nil { | |||
fmt.Println(err) | |||
return | |||
} | |||
fmt.Printf("%+v\n", resp) | |||
} | |||
func TestLogicExtServer_protoUnmarshal(t *testing.T) { | |||
db.InitMysql(config.MySQL) | |||
var msg model.Message | |||
if err := db.DB.Table("message_000").Where("`id` = 194581").First(&msg).Error; err != nil { | |||
if err == gorm.ErrRecordNotFound { | |||
fmt.Println(err) | |||
return | |||
} | |||
} | |||
var temp pb.RED_PACKAGE | |||
//var temp1 pb.RED_PACKAGE | |||
err := proto.Unmarshal(msg.Content, &temp) | |||
if err != nil { | |||
fmt.Println(err) | |||
return | |||
} | |||
//err = proto.Unmarshal(temp.Data, &temp1) | |||
//if err != nil { | |||
// fmt.Println(err) | |||
// return | |||
//} | |||
fmt.Println(temp) | |||
fmt.Printf("!!!!") | |||
} | |||
func TestLogicExtServer_SendImageMessage(t *testing.T) { | |||
buf, err := proto.Marshal(&pb.Image{ | |||
Id: "", | |||
Width: 0, | |||
Height: 0, | |||
Url: "https://img.iplaysoft.com/wp-content/uploads/2019/free-images/free_stock_photo.jpg", | |||
ThumbnailUrl: "", | |||
}) | |||
if err != nil { | |||
fmt.Println(err) | |||
return | |||
} | |||
resp, err := getLogicExtClient().SendMessage(getCtx(), | |||
&pb.SendMessageReq{ | |||
ReceiverType: pb.ReceiverType_RT_USER, | |||
ReceiverId: 1, | |||
ToUserIds: nil, | |||
MessageType: pb.MessageType_MT_IMAGE, | |||
MessageContent: buf, | |||
IsPersist: true, | |||
SendTime: util.UnixMilliTime(time.Now()), | |||
}) | |||
if err != nil { | |||
fmt.Println(err) | |||
return | |||
} | |||
fmt.Printf("%+v\n", resp) | |||
} | |||
func TestLogicExtServer_CreateGroup(t *testing.T) { | |||
resp, err := getLogicExtClient().CreateGroup(getCtx(), | |||
&pb.CreateGroupReq{ | |||
Name: "10", | |||
Introduction: "10", | |||
Extra: "10", | |||
}) | |||
if err != nil { | |||
fmt.Println(err) | |||
return | |||
} | |||
fmt.Printf("%+v\n", resp) | |||
} | |||
func TestLogicExtServer_UpdateGroup(t *testing.T) { | |||
resp, err := getLogicExtClient().UpdateGroup(getCtx(), | |||
&pb.UpdateGroupReq{ | |||
GroupId: 2, | |||
Name: "11", | |||
Introduction: "11", | |||
Extra: "11", | |||
}) | |||
if err != nil { | |||
fmt.Println(err) | |||
return | |||
} | |||
fmt.Printf("%+v\n", resp) | |||
} | |||
func TestLogicExtServer_GetGroup(t *testing.T) { | |||
resp, err := getLogicExtClient().GetGroup(getCtx(), | |||
&pb.GetGroupReq{ | |||
GroupId: 2, | |||
}) | |||
if err != nil { | |||
fmt.Println(err) | |||
return | |||
} | |||
fmt.Printf("%+v\n", resp) | |||
} | |||
func TestLogicExtServer_GetUserGroups(t *testing.T) { | |||
resp, err := getLogicExtClient().GetGroups(getCtx(), &pb.Empty{}) | |||
if err != nil { | |||
fmt.Println(err) | |||
return | |||
} | |||
// todo 不能获取用户所在的超大群组 | |||
fmt.Printf("%+v\n", resp) | |||
} | |||
func TestLogicExtServer_AddGroupMember(t *testing.T) { | |||
resp, err := getLogicExtClient().AddGroupMembers(getCtx(), | |||
&pb.AddGroupMembersReq{ | |||
GroupId: 2, | |||
}) | |||
if err != nil { | |||
fmt.Println(err) | |||
return | |||
} | |||
fmt.Printf("%+v\n", resp) | |||
} | |||
func TestLogicExtServer_UpdateGroupMember(t *testing.T) { | |||
resp, err := getLogicExtClient().UpdateGroupMember(getCtx(), | |||
&pb.UpdateGroupMemberReq{ | |||
GroupId: 2, | |||
UserId: 3, | |||
Remarks: "2", | |||
Extra: "2", | |||
}) | |||
if err != nil { | |||
fmt.Println(err) | |||
return | |||
} | |||
fmt.Printf("%+v\n", resp) | |||
} | |||
func TestLogicExtServer_DeleteGroupMember(t *testing.T) { | |||
resp, err := getLogicExtClient().DeleteGroupMember(getCtx(), | |||
&pb.DeleteGroupMemberReq{ | |||
GroupId: 10, | |||
UserId: 1, | |||
}) | |||
if err != nil { | |||
fmt.Println(err) | |||
return | |||
} | |||
fmt.Printf("%+v\n", resp) | |||
} | |||
func TestLogicExtServer_GetGroupMembers(t *testing.T) { | |||
resp, err := getLogicExtClient().GetGroupMembers(getCtx(), | |||
&pb.GetGroupMembersReq{ | |||
GroupId: 2, | |||
}) | |||
if err != nil { | |||
fmt.Println(err) | |||
return | |||
} | |||
fmt.Printf("%+v\n", resp) | |||
} | |||
func TestLogicExtServer_AddFriend(t *testing.T) { | |||
resp, err := getLogicExtClient().AddFriend(getCtx(), | |||
&pb.AddFriendReq{ | |||
FriendId: 2, | |||
Remarks: "weihan", | |||
Description: "hello", | |||
}) | |||
if err != nil { | |||
fmt.Println(err) | |||
return | |||
} | |||
fmt.Printf("%+v\n", resp) | |||
} |
@@ -0,0 +1,79 @@ | |||
package api | |||
import ( | |||
"context" | |||
"gim/internal/logic/app" | |||
"gim/pkg/logger" | |||
"gim/pkg/pb" | |||
) | |||
type LogicIntServer struct{} | |||
// ConnSignIn 设备登录 | |||
func (*LogicIntServer) ConnSignIn(ctx context.Context, req *pb.ConnSignInReq) (*pb.Empty, error) { | |||
return &pb.Empty{}, | |||
app.DeviceApp.SignIn(ctx, req.UserId, req.DeviceId, req.Token, req.ConnAddr, req.ClientAddr) | |||
} | |||
// Sync 设备同步消息 | |||
func (*LogicIntServer) Sync(ctx context.Context, req *pb.SyncReq) (*pb.SyncResp, error) { | |||
return app.MessageApp.Sync(ctx, req.UserId, req.Seq) | |||
} | |||
// MessageACK 设备收到消息ack | |||
func (*LogicIntServer) MessageACK(ctx context.Context, req *pb.MessageACKReq) (*pb.Empty, error) { | |||
return &pb.Empty{}, app.MessageApp.MessageAck(ctx, req.UserId, req.DeviceId, req.DeviceAck) | |||
} | |||
// Offline 设备离线 | |||
func (*LogicIntServer) Offline(ctx context.Context, req *pb.OfflineReq) (*pb.Empty, error) { | |||
return &pb.Empty{}, app.DeviceApp.Offline(ctx, req.DeviceId, req.ClientAddr) | |||
} | |||
func (s *LogicIntServer) SubscribeRoom(ctx context.Context, req *pb.SubscribeRoomReq) (*pb.Empty, error) { | |||
return &pb.Empty{}, app.RoomApp.SubscribeRoom(ctx, req) | |||
} | |||
// SendMessage 发送消息 | |||
func (*LogicIntServer) SendMessage(ctx context.Context, req *pb.SendMessageReq) (*pb.SendMessageResp, error) { | |||
sender := pb.Sender{ | |||
SenderType: pb.SenderType_ST_BUSINESS, | |||
SenderId: 0, | |||
DeviceId: 0, | |||
} | |||
seq, err := app.MessageApp.SendMessage(ctx, &sender, req) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return &pb.SendMessageResp{Seq: seq}, nil | |||
} | |||
// PushRoom 推送房间 | |||
func (s *LogicIntServer) PushRoom(ctx context.Context, req *pb.PushRoomReq) (*pb.Empty, error) { | |||
return &pb.Empty{}, app.RoomApp.Push(ctx, &pb.Sender{ | |||
SenderType: pb.SenderType_ST_BUSINESS, | |||
}, req) | |||
} | |||
// PushAll 全服推送 | |||
func (s *LogicIntServer) PushAll(ctx context.Context, req *pb.PushAllReq) (*pb.Empty, error) { | |||
return &pb.Empty{}, app.MessageApp.PushAll(ctx, req) | |||
} | |||
// GetDevice 获取设备信息 | |||
func (*LogicIntServer) GetDevice(ctx context.Context, req *pb.GetDeviceReq) (*pb.GetDeviceResp, error) { | |||
device, err := app.DeviceApp.GetDevice(ctx, req.DeviceId) | |||
return &pb.GetDeviceResp{Device: device}, err | |||
} | |||
// ServerStop 服务停止 | |||
func (s *LogicIntServer) ServerStop(ctx context.Context, in *pb.ServerStopReq) (*pb.Empty, error) { | |||
go func() { | |||
err := app.DeviceApp.ServerStop(ctx, in.ConnAddr) | |||
if err != nil { | |||
logger.Sugar.Error(err) | |||
} | |||
}() | |||
return &pb.Empty{}, nil | |||
} |
@@ -0,0 +1,128 @@ | |||
package api | |||
import ( | |||
"context" | |||
"fmt" | |||
"gim/pkg/logger" | |||
"gim/pkg/pb" | |||
"gim/pkg/util" | |||
"testing" | |||
"time" | |||
"google.golang.org/protobuf/proto" | |||
"google.golang.org/grpc" | |||
"google.golang.org/grpc/metadata" | |||
) | |||
func getLogicIntClient() pb.LogicIntClient { | |||
conn, err := grpc.Dial("111.229.238.28:50000", grpc.WithInsecure()) | |||
if err != nil { | |||
logger.Sugar.Error(err) | |||
return nil | |||
} | |||
return pb.NewLogicIntClient(conn) | |||
} | |||
func TestLogicIntServer_SignIn(t *testing.T) { | |||
token := "" | |||
resp, err := getLogicIntClient().ConnSignIn(context.TODO(), | |||
&pb.ConnSignInReq{ | |||
DeviceId: 1, | |||
UserId: 1, | |||
Token: token, | |||
ConnAddr: "127.0.0.1:5000", | |||
}) | |||
if err != nil { | |||
logger.Sugar.Error(err) | |||
return | |||
} | |||
logger.Sugar.Info(resp) | |||
} | |||
func TestLogicIntServer_Sync(t *testing.T) { | |||
resp, err := getLogicIntClient().Sync(metadata.NewOutgoingContext(context.TODO(), metadata.Pairs("key", "val")), | |||
&pb.SyncReq{ | |||
UserId: 1, | |||
DeviceId: 1, | |||
Seq: 0, | |||
}) | |||
if err != nil { | |||
logger.Sugar.Error(err) | |||
return | |||
} | |||
logger.Sugar.Info(resp) | |||
} | |||
func TestLogicIntServer_MessageACK(t *testing.T) { | |||
resp, err := getLogicIntClient().MessageACK(metadata.NewOutgoingContext(context.TODO(), metadata.Pairs("key", "val")), | |||
&pb.MessageACKReq{ | |||
UserId: 1, | |||
DeviceId: 1, | |||
DeviceAck: 1, | |||
ReceiveTime: 1, | |||
}) | |||
if err != nil { | |||
logger.Sugar.Error(err) | |||
return | |||
} | |||
logger.Sugar.Info(resp) | |||
} | |||
func TestLogicIntServer_Offline(t *testing.T) { | |||
resp, err := getLogicIntClient().Offline(metadata.NewOutgoingContext(context.TODO(), metadata.Pairs("key", "val")), | |||
&pb.OfflineReq{ | |||
UserId: 1, | |||
DeviceId: 1, | |||
}) | |||
if err != nil { | |||
logger.Sugar.Error(err) | |||
return | |||
} | |||
logger.Sugar.Info(resp) | |||
} | |||
func TestLogicIntServer_PushRoom(t *testing.T) { | |||
buf, err := proto.Marshal(&pb.Text{ | |||
Text: "hello alber ", | |||
}) | |||
if err != nil { | |||
fmt.Println(err) | |||
return | |||
} | |||
resp, err := getLogicIntClient().PushRoom(getCtx(), | |||
&pb.PushRoomReq{ | |||
RoomId: 1, | |||
MessageType: pb.MessageType_MT_TEXT, | |||
MessageContent: buf, | |||
SendTime: util.UnixMilliTime(time.Now()), | |||
IsPersist: true, | |||
}) | |||
if err != nil { | |||
fmt.Println(err) | |||
return | |||
} | |||
fmt.Printf("%+v\n", resp) | |||
} | |||
func TestLogicIntServer_PushAll(t *testing.T) { | |||
buf, err := proto.Marshal(&pb.Text{ | |||
Text: "hello alber ", | |||
}) | |||
if err != nil { | |||
fmt.Println(err) | |||
return | |||
} | |||
resp, err := getLogicIntClient().PushAll(getCtx(), | |||
&pb.PushAllReq{ | |||
MessageType: pb.MessageType_MT_TEXT, | |||
MessageContent: buf, | |||
SendTime: util.UnixMilliTime(time.Now()), | |||
}) | |||
if err != nil { | |||
fmt.Println(err) | |||
return | |||
} | |||
fmt.Printf("%+v\n", resp) | |||
} |
@@ -0,0 +1,85 @@ | |||
package app | |||
import ( | |||
"context" | |||
devicedomain "gim/internal/logic/domain/device" | |||
"gim/pkg/gerrors" | |||
"gim/pkg/pb" | |||
) | |||
type deviceApp struct{} | |||
var DeviceApp = new(deviceApp) | |||
// Register 注册设备 | |||
func (*deviceApp) Register(ctx context.Context, in *pb.RegisterDeviceReq) (int64, error) { | |||
device := devicedomain.Device{ | |||
Type: in.Type, | |||
Brand: in.Brand, | |||
Model: in.Model, | |||
SystemVersion: in.SystemVersion, | |||
SDKVersion: in.SdkVersion, | |||
} | |||
// 判断设备信息是否合法 | |||
if !device.IsLegal() { | |||
return 0, gerrors.ErrBadRequest | |||
} | |||
err := devicedomain.DeviceRepo.Save(&device) | |||
if err != nil { | |||
return 0, err | |||
} | |||
return device.Id, nil | |||
} | |||
// SignIn 登录 | |||
func (*deviceApp) SignIn(ctx context.Context, userId, deviceId int64, token string, connAddr string, clientAddr string) error { | |||
return devicedomain.DeviceService.SignIn(ctx, userId, deviceId, token, connAddr, clientAddr) | |||
} | |||
// Offline 设备离线 | |||
func (*deviceApp) Offline(ctx context.Context, deviceId int64, clientAddr string) error { | |||
device, err := devicedomain.DeviceRepo.Get(deviceId) | |||
if err != nil { | |||
return err | |||
} | |||
if device == nil { | |||
return nil | |||
} | |||
if device.ClientAddr != clientAddr { | |||
return nil | |||
} | |||
device.Status = devicedomain.DeviceOffLine | |||
err = devicedomain.DeviceRepo.Save(device) | |||
if err != nil { | |||
return err | |||
} | |||
return nil | |||
} | |||
// ListOnlineByUserId 获取用户所有在线设备 | |||
func (*deviceApp) ListOnlineByUserId(ctx context.Context, userId int64) ([]*pb.Device, error) { | |||
return devicedomain.DeviceService.ListOnlineByUserId(ctx, userId) | |||
} | |||
// GetDevice 获取设备信息 | |||
func (*deviceApp) GetDevice(ctx context.Context, deviceId int64) (*pb.Device, error) { | |||
device, err := devicedomain.DeviceRepo.Get(deviceId) | |||
if err != nil { | |||
return nil, err | |||
} | |||
if device == nil { | |||
return nil, gerrors.ErrDeviceNotExist | |||
} | |||
return device.ToProto(), err | |||
} | |||
// ServerStop connect服务停止 | |||
func (*deviceApp) ServerStop(ctx context.Context, connAddr string) error { | |||
return devicedomain.DeviceService.ServerStop(ctx, connAddr) | |||
} |
@@ -0,0 +1,68 @@ | |||
package app | |||
import ( | |||
"context" | |||
frienddomain "gim/internal/logic/domain/friend" | |||
"gim/pkg/pb" | |||
"time" | |||
) | |||
type friendApp struct{} | |||
var FriendApp = new(friendApp) | |||
// List 获取好友列表 | |||
func (s *friendApp) List(ctx context.Context, userId int64) ([]*pb.Friend, error) { | |||
return frienddomain.FriendService.List(ctx, userId) | |||
} | |||
// AddFriend 添加好友 | |||
func (*friendApp) AddFriend(ctx context.Context, userId, friendId int64, remarks, description string) error { | |||
return frienddomain.FriendService.AddFriend(ctx, userId, friendId, remarks, description) | |||
} | |||
// DeleteFriend 添加好友 | |||
func (*friendApp) DeleteFriend(ctx context.Context, userId, friendId int64) error { | |||
return frienddomain.FriendService.DeleteFriend(ctx, userId, friendId) | |||
} | |||
// AgreeAddFriend 同意添加好友 | |||
func (*friendApp) AgreeAddFriend(ctx context.Context, userId, friendId int64, remarks string) error { | |||
return frienddomain.FriendService.AgreeAddFriend(ctx, userId, friendId, remarks) | |||
} | |||
// SetFriend 设置好友信息 | |||
func (*friendApp) SetFriend(ctx context.Context, userId int64, req *pb.SetFriendReq) error { | |||
friend, err := frienddomain.FriendRepo.Get(userId, req.FriendId) | |||
if err != nil { | |||
return err | |||
} | |||
if friend == nil { | |||
return nil | |||
} | |||
friend.Remarks = req.Remarks | |||
friend.Extra = req.Extra | |||
friend.UpdateTime = time.Now() | |||
err = frienddomain.FriendRepo.Save(friend) | |||
if err != nil { | |||
return err | |||
} | |||
return nil | |||
} | |||
// SendToFriend 消息发送至好友 | |||
func (*friendApp) SendToFriend(ctx context.Context, sender *pb.Sender, req *pb.SendMessageReq) (int64, error) { | |||
return frienddomain.FriendService.SendToFriend(ctx, sender, req) | |||
} | |||
// RecallMessageSendToFriend 撤回消息发送至好友 | |||
func (*friendApp) RecallMessageSendToFriend(ctx context.Context, sender *pb.Sender, req *pb.RecallMessageReq) (int64, error) { | |||
return frienddomain.FriendService.RecallMessageSendToFriend(ctx, sender, req) | |||
} | |||
// RedPackageMessageSendToFriend 红包发送至好友 | |||
func (*friendApp) RedPackageMessageSendToFriend(ctx context.Context, sender *pb.Sender, req *pb.SendRedPacketReq) (int64, error) { | |||
return frienddomain.FriendService.RedPackageMessageSendToFriend(ctx, sender, req) | |||
} |
@@ -0,0 +1,427 @@ | |||
package app | |||
import ( | |||
"context" | |||
"database/sql" | |||
"errors" | |||
"gim/internal/business/comm/utils" | |||
repo2 "gim/internal/business/domain/user/repo" | |||
"gim/internal/logic/domain/group/model" | |||
"gim/internal/logic/domain/group/repo" | |||
"gim/pkg/db" | |||
"gim/pkg/grpclib" | |||
"gim/pkg/pb" | |||
"strings" | |||
) | |||
type groupApp struct{} | |||
var GroupApp = new(groupApp) | |||
// CreateGroup 创建群组 | |||
func (*groupApp) CreateGroup(ctx context.Context, userId int64, in *pb.CreateGroupReq) (int64, error) { | |||
masterId, err := grpclib.GetCtxMasterId(ctx) | |||
if err != nil { | |||
return 0, err | |||
} | |||
group := model.CreateGroup(userId, utils.StrToInt64(masterId), in) | |||
err = repo.GroupRepo.Save(group) | |||
if err != nil { | |||
return 0, err | |||
} | |||
in.MemberIds = append(in.MemberIds, userId) | |||
err = group.PushAddMember(ctx, userId, in.MemberIds, false) | |||
return group.Id, nil | |||
} | |||
// GetGroup 获取群组信息 | |||
func (*groupApp) GetGroup(ctx context.Context, groupId int64) (*pb.Group, pb.MemberType, pb.GroupUserStatusType, error) { | |||
group, err := repo.GroupRepo.Get(groupId) | |||
memberType := pb.MemberType_GMT_UNKNOWN | |||
userStatusType := pb.GroupUserStatusType_GROUP_USER_STATUS_NORMAL | |||
if err != nil { | |||
return nil, memberType, userStatusType, err | |||
} | |||
masterId, err := grpclib.GetCtxMasterId(ctx) | |||
if err != nil { | |||
return nil, memberType, userStatusType, err | |||
} | |||
userId, _, err := grpclib.GetCtxData(ctx) | |||
if err != nil { | |||
return nil, memberType, userStatusType, err | |||
} | |||
if !strings.Contains(group.Name, "官方") && !strings.Contains(group.Name, "运营中心") && masterId == "68823769" { | |||
for _, member := range group.Members { | |||
if member.UserId == userId && member.MemberType != int(pb.MemberType_GMT_ADMIN) { | |||
group.Name = "我的推荐人群" | |||
} | |||
} | |||
} | |||
for _, member := range group.Members { | |||
if member.UserId == userId { | |||
memberType = pb.MemberType(member.MemberType) | |||
userStatusType = pb.GroupUserStatusType(member.Status) | |||
} | |||
} | |||
return group.ToProto(), memberType, userStatusType, nil | |||
} | |||
// GetUserGroups 获取用户加入的群组列表 | |||
func (*groupApp) GetUserGroups(ctx context.Context, userId int64) ([]*pb.Group, error) { | |||
masterId, err := grpclib.GetCtxMasterId(ctx) | |||
if err != nil { | |||
return nil, err | |||
} | |||
//user, err := repo2.UserRepo.Get(userId) | |||
//if err != nil { | |||
// return nil, err | |||
//} | |||
groups, err := repo.GroupUserRepo.ListByUserId(userId) | |||
if err != nil { | |||
return nil, err | |||
} | |||
pbGroups := make([]*pb.Group, len(groups)) | |||
for i := range groups { | |||
pbGroups[i] = groups[i].ToProto() | |||
//if !strings.Contains(pbGroups[i].Introduction, user.PhoneNumber) && !strings.Contains(pbGroups[i].Name, "官方") && !strings.Contains(pbGroups[i].Name, "运营中心") && masterId == "68823769" { | |||
// pbGroups[i].Name = "我的推荐人群" | |||
//} | |||
groupUser, err := repo.GroupUserRepo.Get(pbGroups[i].GroupId, userId) | |||
if err != nil { | |||
return nil, err | |||
} | |||
if !strings.Contains(pbGroups[i].Name, "官方") && !strings.Contains(pbGroups[i].Name, "运营中心") && masterId == "68823769" { | |||
if groupUser.MemberType != int(pb.MemberType_GMT_ADMIN) { | |||
pbGroups[i].Name = "我的推荐人群" | |||
} | |||
} | |||
} | |||
return pbGroups, nil | |||
} | |||
// Update 更新群组 | |||
func (*groupApp) Update(ctx context.Context, userId int64, update *pb.UpdateGroupReq) error { | |||
group, err := repo.GroupRepo.Get(update.GroupId) | |||
if err != nil { | |||
return err | |||
} | |||
var isUpdateIntroduction = false | |||
if group.Introduction != update.Introduction { | |||
isUpdateIntroduction = true | |||
} | |||
err = group.Update(ctx, update) | |||
if err != nil { | |||
return err | |||
} | |||
err = repo.GroupRepo.Save(group) | |||
if err != nil { | |||
return err | |||
} | |||
err = group.PushUpdate(ctx, userId, isUpdateIntroduction) | |||
if err != nil { | |||
return err | |||
} | |||
return nil | |||
} | |||
// AddMembers 添加群组成员 | |||
func (*groupApp) AddMembers(ctx context.Context, userId, groupId int64, userIds []int64, masterId string) ([]int64, error) { | |||
group, err := repo.GroupRepo.Get(groupId) | |||
if err != nil { | |||
return nil, err | |||
} | |||
master, err := repo2.MasterRepo.Get(utils.StrToInt64(masterId)) | |||
if err != nil { | |||
return nil, err | |||
} | |||
imPackage, err := repo2.ImPackageRepo.Get(master.PackageId) | |||
if err != nil { | |||
return nil, err | |||
} | |||
if imPackage.GroupNum < int64(group.UserNum)+int64(len(userIds)) { | |||
return nil, errors.New("群人员数量已满!") | |||
} | |||
existIds, addedIds, err := group.AddMembers(ctx, userIds) | |||
if err != nil { | |||
return nil, err | |||
} | |||
err = repo.GroupRepo.Save(group) | |||
if err != nil { | |||
return nil, err | |||
} | |||
err = group.PushAddMember(ctx, userId, addedIds, true) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return existIds, nil | |||
} | |||
// UpdateMember 更新群组用户 | |||
func (*groupApp) UpdateMember(ctx context.Context, in *pb.UpdateGroupMemberReq, optId int64) error { | |||
group, err := repo.GroupRepo.Get(in.GroupId) | |||
if err != nil { | |||
return err | |||
} | |||
err = group.UpdateMember(ctx, in) | |||
if err != nil { | |||
return err | |||
} | |||
err = repo.GroupRepo.Save(group) | |||
if err != nil { | |||
return err | |||
} | |||
err = group.PushUpdateMember(ctx, optId, in.UserId, int32(in.MemberType)) | |||
if err != nil { | |||
return err | |||
} | |||
return nil | |||
} | |||
// DeleteMember 删除群组成员 | |||
func (*groupApp) DeleteMember(ctx context.Context, groupId int64, userId int64, optId int64) error { | |||
group, err := repo.GroupRepo.Get(groupId) | |||
if err != nil { | |||
return err | |||
} | |||
err = group.DeleteMember(ctx, userId) | |||
if err != nil { | |||
return err | |||
} | |||
group.UserNum-- //群组人数减一 | |||
err = repo.GroupRepo.Save(group) | |||
if err != nil { | |||
return err | |||
} | |||
if userId == optId { //自己退出群聊,无需发送消息 | |||
return nil | |||
} | |||
err = group.PushDeleteMember(ctx, optId, userId) | |||
if err != nil { | |||
return err | |||
} | |||
return nil | |||
} | |||
// GetMembers 获取群组成员 | |||
func (*groupApp) GetMembers(ctx context.Context, groupId, limit int64) ([]*pb.GroupMember, error) { | |||
if limit == 0 { | |||
group, err := repo.GroupRepo.Get(groupId) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return group.GetMembers(ctx) | |||
} else { | |||
group, err := repo.GroupRepo.GetLimit(groupId, limit) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return group.GetMembers(ctx) | |||
} | |||
} | |||
// SendMessage 发送群组消息 | |||
func (*groupApp) SendMessage(ctx context.Context, sender *pb.Sender, req *pb.SendMessageReq) (int64, error) { | |||
group, err := repo.GroupRepo.Get(req.ReceiverId) | |||
if err != nil { | |||
return 0, err | |||
} | |||
return group.SendMessage(ctx, sender, req) | |||
} | |||
// GetBannedMembers 获取群组禁言成员 | |||
func (*groupApp) GetBannedMembers(ctx context.Context, groupId int64) ([]*pb.GroupMember, error) { | |||
// 查询被禁言的成员列表 | |||
group, err := repo.GroupRepo.GetForBanned(groupId) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return group.GetMembers(ctx) | |||
} | |||
// SetGroupMemberBanned 设置禁言 | |||
func (*groupApp) SetGroupMemberBanned(ctx context.Context, optId, groupId int64, userIds []int64, isAllMemberBanned bool) ([]*pb.GroupMember, error) { | |||
groupUser, err := repo.GroupUserRepo.Get(groupId, optId) | |||
if err != nil { | |||
return nil, err | |||
} | |||
if groupUser == nil { | |||
return nil, errors.New("未查询到群组用户信息") | |||
} | |||
if groupUser.MemberType != int(pb.MemberType_GMT_ADMIN) && groupUser.MemberType != int(pb.MemberType_GMT_MANAGE) { | |||
return nil, errors.New("非管理员操作") | |||
} | |||
group, err := repo.GroupRepo.Get(groupId) | |||
if err != nil { | |||
return nil, err | |||
} | |||
if isAllMemberBanned { | |||
//设置全员禁言 | |||
//1、更新 `group` 的 is_all_member_banned 状态 | |||
group.IsAllMemberBanned = int32(pb.AllMemberBannedType_YES_All_Member_Banned) | |||
err := repo.GroupRepo.Save(group) | |||
if err != nil { | |||
return nil, err | |||
} | |||
//2、发送推送消息 | |||
err = group.PushGroupMemberBanned(ctx, optId, 0, isAllMemberBanned) | |||
if err != nil { | |||
return nil, err | |||
} | |||
} else { | |||
//设置成员禁言 | |||
//1、更新 `group` 的 is_all_member_banned 状态 | |||
if group.IsAllMemberBanned != int32(pb.AllMemberBannedType_NOT_All_Member_Banned) { | |||
group.IsAllMemberBanned = int32(pb.AllMemberBannedType_NOT_All_Member_Banned) | |||
err := repo.GroupRepo.Save(group) | |||
if err != nil { | |||
return nil, err | |||
} | |||
//TODO::推送消息 | |||
err = group.PushGroupMemberBanned(ctx, optId, -1, isAllMemberBanned) | |||
if err != nil { | |||
return nil, err | |||
} | |||
} | |||
//2、更新 `group_user` 的 status 状态 | |||
if len(userIds) == 0 { | |||
db.DB.Model(model.GroupUser{}).Where("group_id = ?", groupId).Updates(model.GroupUserV2{Status: sql.NullInt32{Int32: int32(pb.GroupUserStatusType_GROUP_USER_STATUS_NORMAL), Valid: true}}) | |||
} else { | |||
db.DB.Model(model.GroupUser{}).Where("user_id in (?) and group_id = ?", userIds, groupId).Updates(model.GroupUserV2{Status: sql.NullInt32{Int32: int32(pb.GroupUserStatusType_GROUP_USER_STATUS_Banned), Valid: true}}) | |||
db.DB.Model(model.GroupUser{}).Where("(user_id) not in (?) and group_id = ?", userIds, groupId).Updates(model.GroupUserV2{Status: sql.NullInt32{Int32: int32(pb.GroupUserStatusType_GROUP_USER_STATUS_NORMAL), Valid: true}}) | |||
} | |||
//3、删除缓存 | |||
err := repo.GroupCache.Del(groupId) | |||
if err != nil { | |||
return nil, err | |||
} | |||
//4、发送推送消息 | |||
for _, u := range userIds { | |||
err = group.PushGroupMemberBanned(ctx, optId, u, isAllMemberBanned) | |||
if err != nil { | |||
return nil, err | |||
} | |||
} | |||
} | |||
// 查询被禁言的成员列表 | |||
group, err = repo.GroupRepo.GetForBanned(groupId) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return group.GetMembers(ctx) | |||
} | |||
// SetGroupMemberRemoveBanned 设置取消禁言 | |||
func (*groupApp) SetGroupMemberRemoveBanned(ctx context.Context, optId, groupId int64, removeUserIds []int64) error { | |||
groupUser, err := repo.GroupUserRepo.Get(groupId, optId) | |||
if err != nil { | |||
return err | |||
} | |||
if groupUser == nil { | |||
return errors.New("未查询到群组用户信息") | |||
} | |||
if groupUser.MemberType != int(pb.MemberType_GMT_ADMIN) && groupUser.MemberType != int(pb.MemberType_GMT_MANAGE) { | |||
return errors.New("非管理员操作") | |||
} | |||
group, err := repo.GroupRepo.Get(groupId) | |||
if err != nil { | |||
return err | |||
} | |||
if len(removeUserIds) > 0 { | |||
//1、更新 `group_user` 的 status 状态 | |||
db.DB.Model(model.GroupUser{}).Where("user_id in (?) and group_id = ?", removeUserIds, groupId).Updates(model.GroupUserV2{Status: sql.NullInt32{Int32: int32(pb.GroupUserStatusType_GROUP_USER_STATUS_NORMAL), Valid: true}}) | |||
//2、删除缓存 | |||
err := repo.GroupCache.Del(groupId) | |||
if err != nil { | |||
return err | |||
} | |||
//3、给取消禁言用户推送消息 | |||
for _, u := range removeUserIds { | |||
err = group.PushGroupMemberRemoveBanned(ctx, optId, u) | |||
if err != nil { | |||
return err | |||
} | |||
} | |||
} | |||
return nil | |||
} | |||
// RecallSendMessage 撤回发送消息 | |||
func (*groupApp) RecallSendMessage(ctx context.Context, sender *pb.Sender, req *pb.RecallMessageReq) (int64, error) { | |||
group, err := repo.GroupRepo.Get(req.ReceiverId) | |||
if err != nil { | |||
return 0, err | |||
} | |||
return group.RecallSendMessage(ctx, sender, req) | |||
} | |||
// SendRedPackage 发送红包消息 | |||
func (*groupApp) SendRedPackage(ctx context.Context, sender *pb.Sender, req *pb.SendRedPacketReq) (int64, error) { | |||
group, err := repo.GroupRepo.Get(req.ReceiverId) | |||
if err != nil { | |||
return 0, err | |||
} | |||
return group.SendRedPackage(ctx, sender, req) | |||
} | |||
// SetGroupAddFriend 设置是否加好友 | |||
func (*groupApp) SetGroupAddFriend(ctx context.Context, optId int64, req *pb.SetGroupAddFriendReq) error { | |||
//1、判断是否为管理员操作 | |||
groupUser, err := repo.GroupUserRepo.Get(req.GroupId, optId) | |||
if err != nil { | |||
return err | |||
} | |||
if groupUser == nil { | |||
return errors.New("未查询到群组用户信息") | |||
} | |||
if groupUser.MemberType != int(pb.MemberType_GMT_ADMIN) && groupUser.MemberType != int(pb.MemberType_GMT_MANAGE) { | |||
return errors.New("非管理员操作") | |||
} | |||
//group, err := repo.GroupRepo.Get(req.GroupId) | |||
//if err != nil { | |||
// return err | |||
//} | |||
//2、更改群组信息 | |||
db.DB.Model(model.Group{}).Where("id = ?", req.GroupId).Updates(model.Group{IsAllAddFriend: int32(req.IsAllAddFriend)}) | |||
//3、删除缓存 | |||
err = repo.GroupCache.Del(req.GroupId) | |||
if err != nil { | |||
return err | |||
} | |||
////4、发送推送消息 | |||
//for _, u := range userIds { | |||
// err = group.PushGroupMemberBanned(ctx, optId, u, isAllMemberBanned) | |||
// if err != nil { | |||
// return nil, err | |||
// } | |||
//} | |||
return nil | |||
} |
@@ -0,0 +1,110 @@ | |||
package app | |||
import ( | |||
"context" | |||
"gim/internal/logic/domain/message/service" | |||
"gim/pkg/pb" | |||
"time" | |||
"google.golang.org/protobuf/proto" | |||
) | |||
type messageApp struct{} | |||
var MessageApp = new(messageApp) | |||
// SendToUser 发送消息给用户 | |||
func (*messageApp) SendToUser(ctx context.Context, sender *pb.Sender, toUserId int64, req *pb.SendMessageReq) (int64, error) { | |||
return service.MessageService.SendToUser(ctx, sender, toUserId, req) | |||
} | |||
// RecallMessageSendToUser 撤回消息发送给用户 | |||
func (*messageApp) RecallMessageSendToUser(ctx context.Context, sender *pb.Sender, toUserId int64, req *pb.RecallMessageReq, sendTime time.Time) (int64, error) { | |||
return service.MessageService.RecallMessageSendToUser(ctx, sender, toUserId, req, sendTime) | |||
} | |||
// SendRedPackageToUser 发送红包给用户 | |||
func (*messageApp) SendRedPackageToUser(ctx context.Context, sender *pb.Sender, toUserId int64, req *pb.SendRedPacketReq) (int64, error) { | |||
return service.MessageService.SendRedPackageToUser(ctx, sender, toUserId, req) | |||
} | |||
// PushToUser 推送消息给用户 | |||
func (*messageApp) PushToUser(ctx context.Context, userId int64, code pb.PushCode, message proto.Message, isPersist bool) error { | |||
return service.PushService.PushToUser(ctx, userId, code, message, isPersist) | |||
} | |||
// PushAll 全服推送 | |||
func (*messageApp) PushAll(ctx context.Context, req *pb.PushAllReq) error { | |||
return service.PushService.PushAll(ctx, req) | |||
} | |||
// Sync 消息同步 | |||
func (*messageApp) Sync(ctx context.Context, userId, seq int64) (*pb.SyncResp, error) { | |||
return service.MessageService.Sync(ctx, userId, seq) | |||
} | |||
// MessageAck 收到消息回执 | |||
func (*messageApp) MessageAck(ctx context.Context, userId, deviceId, ack int64) error { | |||
return service.DeviceAckService.Update(ctx, userId, deviceId, ack) | |||
} | |||
// SendMessage 发送消息 | |||
func (s *messageApp) SendMessage(ctx context.Context, sender *pb.Sender, req *pb.SendMessageReq) (int64, error) { | |||
// 如果发送者是用户,需要补充用户的信息 | |||
service.MessageService.AddSenderInfo(sender) | |||
switch req.ReceiverType { | |||
// 消息接收者为用户 | |||
case pb.ReceiverType_RT_USER: | |||
// 发送者为用户 | |||
if sender.SenderType == pb.SenderType_ST_USER { | |||
return FriendApp.SendToFriend(ctx, sender, req) | |||
} else { | |||
return s.SendToUser(ctx, sender, req.ReceiverId, req) | |||
} | |||
// 消息接收者是群组 | |||
case pb.ReceiverType_RT_GROUP: | |||
return GroupApp.SendMessage(ctx, sender, req) | |||
} | |||
return 0, nil | |||
} | |||
// RecallMessage 撤回消息 | |||
func (s *messageApp) RecallMessage(ctx context.Context, sender *pb.Sender, req *pb.RecallMessageReq) (int64, error) { | |||
// 如果发送者是用户,需要补充用户的信息 | |||
service.MessageService.AddSenderInfo(sender) | |||
switch req.ReceiverType { | |||
// 消息接收者为用户 | |||
case pb.ReceiverType_RT_USER: | |||
// 发送者为用户 | |||
if sender.SenderType == pb.SenderType_ST_USER { | |||
return FriendApp.RecallMessageSendToFriend(ctx, sender, req) | |||
} else { | |||
//return s.RecallMessageSendToUser(ctx, sender, req.ReceiverId, req, true) | |||
} | |||
// 消息接收者是群组 | |||
case pb.ReceiverType_RT_GROUP: | |||
return GroupApp.RecallSendMessage(ctx, sender, req) | |||
} | |||
return 0, nil | |||
} | |||
// SendRedPackage 发送红包消息 | |||
func (s *messageApp) SendRedPackage(ctx context.Context, sender *pb.Sender, req *pb.SendRedPacketReq) (int64, error) { | |||
// 如果发送者是用户,需要补充用户的信息 | |||
service.MessageService.AddSenderInfo(sender) | |||
switch req.ReceiverType { | |||
// 消息接收者为用户 | |||
case pb.ReceiverType_RT_USER: | |||
// 发送者为用户 | |||
if sender.SenderType == pb.SenderType_ST_USER { | |||
return FriendApp.RedPackageMessageSendToFriend(ctx, sender, req) | |||
} else { | |||
return s.SendRedPackageToUser(ctx, sender, req.ReceiverId, req) | |||
} | |||
// 消息接收者是群组 | |||
case pb.ReceiverType_RT_GROUP: | |||
return GroupApp.SendRedPackage(ctx, sender, req) | |||
} | |||
return 0, nil | |||
} |
@@ -0,0 +1,21 @@ | |||
package app | |||
import ( | |||
"context" | |||
"gim/internal/logic/domain/room" | |||
"gim/pkg/pb" | |||
) | |||
type roomApp struct{} | |||
var RoomApp = new(roomApp) | |||
// Push 推送房间消息 | |||
func (s *roomApp) Push(ctx context.Context, sender *pb.Sender, req *pb.PushRoomReq) error { | |||
return room.RoomService.Push(ctx, sender, req) | |||
} | |||
// SubscribeRoom 订阅房间 | |||
func (s *roomApp) SubscribeRoom(ctx context.Context, req *pb.SubscribeRoomReq) error { | |||
return room.RoomService.SubscribeRoom(ctx, req) | |||
} |
@@ -0,0 +1,66 @@ | |||
package device | |||
import ( | |||
"gim/pkg/pb" | |||
"time" | |||
) | |||
const ( | |||
DeviceOnLine = 1 // 设备在线 | |||
DeviceOffLine = 0 // 设备离线 | |||
) | |||
// Device 设备 | |||
type Device struct { | |||
Id int64 // 设备id | |||
UserId int64 // 用户id | |||
Type int32 // 设备类型,1:Android;2:IOS;3:Windows; 4:MacOS;5:Web | |||
Brand string // 手机厂商 | |||
Model string // 机型 | |||
SystemVersion string // 系统版本 | |||
SDKVersion string // SDK版本 | |||
Status int32 // 在线状态,0:离线;1:在线 | |||
ConnAddr string // 连接层服务层地址 | |||
ClientAddr string // 客户端地址 | |||
CreateTime time.Time // 创建时间 | |||
UpdateTime time.Time // 更新时间 | |||
} | |||
func (d *Device) ToProto() *pb.Device { | |||
return &pb.Device{ | |||
DeviceId: d.Id, | |||
UserId: d.UserId, | |||
Type: d.Type, | |||
Brand: d.Brand, | |||
Model: d.Model, | |||
SystemVersion: d.SystemVersion, | |||
SdkVersion: d.SDKVersion, | |||
Status: d.Status, | |||
ConnAddr: d.ConnAddr, | |||
ClientAddr: d.ClientAddr, | |||
CreateTime: d.CreateTime.Unix(), | |||
UpdateTime: d.UpdateTime.Unix(), | |||
} | |||
} | |||
func (d *Device) IsLegal() bool { | |||
if d.Type == 0 || d.Brand == "" || d.Model == "" || | |||
d.SystemVersion == "" || d.SDKVersion == "" { | |||
return false | |||
} | |||
return true | |||
} | |||
func (d *Device) Online(userId int64, connAddr string, clientAddr string) { | |||
d.UserId = userId | |||
d.ConnAddr = connAddr | |||
d.ClientAddr = clientAddr | |||
d.Status = DeviceOnLine | |||
} | |||
func (d *Device) Offline(userId int64, connAddr string, clientAddr string) { | |||
d.UserId = userId | |||
d.ConnAddr = connAddr | |||
d.ClientAddr = clientAddr | |||
d.Status = DeviceOnLine | |||
} |
@@ -0,0 +1,78 @@ | |||
package device | |||
import ( | |||
"gim/internal/business/domain/user/model" | |||
"gim/pkg/db" | |||
"gim/pkg/gerrors" | |||
"time" | |||
"github.com/jinzhu/gorm" | |||
) | |||
type deviceDao struct{} | |||
var DeviceDao = new(deviceDao) | |||
// Save 插入一条设备信息 | |||
func (*deviceDao) Save(device *Device) error { | |||
device.CreateTime = time.Now() | |||
device.UpdateTime = time.Now() | |||
err := db.DB.Save(&device).Error | |||
if err != nil { | |||
return gerrors.WrapError(err) | |||
} | |||
return nil | |||
} | |||
// Get 获取设备 | |||
func (*deviceDao) Get(deviceId int64) (*Device, error) { | |||
var device = Device{Id: deviceId} | |||
err := db.DB.First(&device).Error | |||
if err != nil && err != gorm.ErrRecordNotFound { | |||
return nil, gerrors.WrapError(err) | |||
} | |||
if err == gorm.ErrRecordNotFound { | |||
return nil, nil | |||
} | |||
return &device, nil | |||
} | |||
// ListOnlineByUserId 查询用户所有的在线设备 | |||
func (*deviceDao) ListOnlineByUserId(userId int64) ([]Device, error) { | |||
var devices []Device | |||
err := db.DB.Find(&devices, "user_id = ? and status = ?", userId, DeviceOnLine).Error | |||
if err != nil { | |||
return nil, gerrors.WrapError(err) | |||
} | |||
return devices, nil | |||
} | |||
// ListOffLineByUserId 查询用户所有的离线设备 | |||
func (*deviceDao) ListOffLineByUserId(userId int64) ([]Device, error) { | |||
var devices []Device | |||
err := db.DB.Find(&devices, "user_id = ? and status = ?", userId, DeviceOnLine).Error | |||
if err != nil { | |||
return nil, gerrors.WrapError(err) | |||
} | |||
return devices, nil | |||
} | |||
// ListOnlineByConnAddr 查询用户所有的在线设备 | |||
func (*deviceDao) ListOnlineByConnAddr(connAddr string) ([]Device, error) { | |||
var devices []Device | |||
err := db.DB.Find(&devices, "conn_addr = ? and status = ?", connAddr, DeviceOnLine).Error | |||
if err != nil { | |||
return nil, gerrors.WrapError(err) | |||
} | |||
return devices, nil | |||
} | |||
// UpdateStatus 更新在线状态 | |||
func (*deviceDao) UpdateStatus(deviceId int64, connAddr string, status int) (int64, error) { | |||
db := db.DB.Model(&model.Device{}).Where("id = ? and conn_addr = ?", deviceId, connAddr). | |||
Update("status", status) | |||
if db.Error != nil { | |||
return 0, gerrors.WrapError(db.Error) | |||
} | |||
return db.RowsAffected, nil | |||
} |
@@ -0,0 +1,38 @@ | |||
package device | |||
import ( | |||
"fmt" | |||
"gim/pkg/db" | |||
"testing" | |||
) | |||
func init() { | |||
fmt.Println("start") | |||
db.InitByTest() | |||
} | |||
func TestDeviceDao_Add(t *testing.T) { | |||
device := Device{ | |||
UserId: 1, | |||
Type: 1, | |||
Brand: "huawei", | |||
Model: "huawei P10", | |||
SystemVersion: "8.0.0", | |||
SDKVersion: "1.0.0", | |||
Status: 1, | |||
} | |||
err := DeviceDao.Save(&device) | |||
fmt.Println(err) | |||
fmt.Println(device) | |||
} | |||
func TestDeviceDao_Get(t *testing.T) { | |||
device, err := DeviceDao.Get(1) | |||
fmt.Printf("%+v\n %+v\n", device, err) | |||
} | |||
func TestDeviceDao_ListOnlineByUserId(t *testing.T) { | |||
devices, err := DeviceDao.ListOnlineByUserId(1) | |||
fmt.Println(err) | |||
fmt.Printf("%+v \n", devices) | |||
} |