初始化项目
This commit is contained in:
57
Dockerfile
Normal file
57
Dockerfile
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
FROM golang:1.25.3
|
||||||
|
RUN go env -w GO111MODULE=on
|
||||||
|
RUN go env -w GOPROXY=https://goproxy.cn,direct
|
||||||
|
ENV WORKDIR /usr/local/bin/app
|
||||||
|
WORKDIR $WORKDIR
|
||||||
|
|
||||||
|
ENV TIME_ZONE=Asia/Seoul
|
||||||
|
RUN useradd -ms /bin/bash golang
|
||||||
|
|
||||||
|
|
||||||
|
RUN ln -sf /usr/share/zoneinfo/$TIME_ZONE /etc/localtime \
|
||||||
|
&& chmod 0755 /usr/bin/wall \
|
||||||
|
&& chmod 0755 /usr/bin/passwd \
|
||||||
|
&& chmod 0755 /usr/bin/newgrp \
|
||||||
|
&& chmod 0755 /usr/bin/chfn \
|
||||||
|
&& chmod 0755 /usr/bin/chage \
|
||||||
|
&& chmod 0755 /usr/bin/gpasswd \
|
||||||
|
&& chmod 0755 /usr/bin/chsh \
|
||||||
|
&& chmod 0755 /usr/bin/expiry \
|
||||||
|
&& chmod 0755 /bin/umount \
|
||||||
|
&& chmod 0755 /bin/mount \
|
||||||
|
&& chmod 0755 /bin/su \
|
||||||
|
&& chmod 0755 /sbin/unix_chkpwd \
|
||||||
|
&& chmod 110 /usr/bin/chfn \
|
||||||
|
&& chmod 110 /usr/bin/passwd \
|
||||||
|
&& chmod 110 /usr/bin/newgrp \
|
||||||
|
&& chmod 110 /usr/bin/chsh \
|
||||||
|
&& chmod 110 /usr/bin/wall \
|
||||||
|
&& chmod 110 /usr/bin/gpasswd \
|
||||||
|
&& chmod 110 /usr/bin/expiry \
|
||||||
|
&& chmod 110 /usr/bin/chage \
|
||||||
|
&& chmod 110 /bin/mount \
|
||||||
|
&& chmod 110 /bin/umount \
|
||||||
|
&& chmod 110 /bin/su \
|
||||||
|
&& chmod 110 /sbin/unix_chkpwd \
|
||||||
|
&& chmod 110 /usr/lib/openssh/ssh-keysign \
|
||||||
|
&& chmod 110 /usr/bin/ssh-agent
|
||||||
|
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
# RUN chown -R golang:golang $WORKDIR
|
||||||
|
RUN go mod download && go mod verify
|
||||||
|
COPY ../../cidService $WORKDIR
|
||||||
|
RUN chown -R golang:golang $WORKDIR
|
||||||
|
# Remove SetUID, SetGID
|
||||||
|
RUN chmod 0755 /usr/local/bin/app/api \
|
||||||
|
&& chmod 0755 /usr/local/bin/app/common \
|
||||||
|
&& chmod 0755 /usr/local/bin/app/middleware \
|
||||||
|
&& chmod 0755 /usr/local/bin/app/model \
|
||||||
|
&& rm -rf .git \
|
||||||
|
&& rm -rf .gitignore \
|
||||||
|
&& rm -rf .gitlab-ci.yml \
|
||||||
|
&& rm -rf Dockerfile \
|
||||||
|
&& rm -rf config-local.yml
|
||||||
|
USER golang
|
||||||
|
RUN go build -v -o /usr/local/bin/app ./...
|
||||||
|
EXPOSE 3002
|
||||||
|
CMD ./cidService
|
||||||
40
config.yml
Normal file
40
config.yml
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
server:
|
||||||
|
address : ":3002"
|
||||||
|
name: "cidService"
|
||||||
|
jwt:
|
||||||
|
secret: "abcdefghijklmnopqrstuvwxyz"
|
||||||
|
rate:
|
||||||
|
limit: 200
|
||||||
|
burst: 300
|
||||||
|
mongo:
|
||||||
|
logger:
|
||||||
|
level: "all"
|
||||||
|
stdout: true
|
||||||
|
address: "mongodb://192.168.3.200:27017/cid_service?retryWrites=true"
|
||||||
|
redis:
|
||||||
|
# 集群模式配置方法
|
||||||
|
default:
|
||||||
|
address: 192.168.3.200:6379
|
||||||
|
db: 0
|
||||||
|
idleTimeout: "60s" #连接最大空闲时间,使用时间字符串例如30s/1m/1d
|
||||||
|
maxConnLifetime: "90s" #连接最长存活时间,使用时间字符串例如30s/1m/1d
|
||||||
|
waitTimeout: "60s" #等待连接池连接的超时时间,使用时间字符串例如30s/1m/1d
|
||||||
|
dialTimeout: "30s" #TCP连接的超时时间,使用时间字符串例如30s/1m/1d
|
||||||
|
readTimeout: "30s" #TCP的Read操作超时时间,使用时间字符串例如30s/1m/1d
|
||||||
|
writeTimeout: "30s" #TCP的Write操作超时时间,使用时间字符串例如30s/1m/1d
|
||||||
|
maxActive: 100
|
||||||
|
consul:
|
||||||
|
address: 192.168.3.200:8500
|
||||||
|
# pass: jiahui8888
|
||||||
|
rabbitMQ:
|
||||||
|
host: 192.168.3.200
|
||||||
|
port: 5672
|
||||||
|
username: guest # 默认用户名
|
||||||
|
password: guest # 默认密码
|
||||||
|
jaeger: #链路追踪
|
||||||
|
addr: 192.168.3.200:4318
|
||||||
|
|
||||||
|
# RAGFlow 智能客服配置
|
||||||
|
ragflow:
|
||||||
|
base_url: "http://192.168.3.200:9380" # RAGFlow 服务地址
|
||||||
|
api_key: "ragflow-your-api-key-here" # RAGFlow API Key(登录 RAGFlow 管理界面 -> 设置 -> API Keys)
|
||||||
1
consts/errors.go
Normal file
1
consts/errors.go
Normal file
@@ -0,0 +1 @@
|
|||||||
|
package consts
|
||||||
1
consts/redis_key.go
Normal file
1
consts/redis_key.go
Normal file
@@ -0,0 +1 @@
|
|||||||
|
package consts
|
||||||
27
controller/data_controller.go
Normal file
27
controller/data_controller.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cidService/model/dto"
|
||||||
|
"cidService/service"
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
type cData struct{}
|
||||||
|
|
||||||
|
var Data = &cData{}
|
||||||
|
|
||||||
|
// Add 添加数据
|
||||||
|
func (c *cData) Add(ctx context.Context, req *dto.AddDataReq) (res *dto.AddDataRes, err error) {
|
||||||
|
return service.Data.Add(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update 更新数据
|
||||||
|
func (c *cData) Update(ctx context.Context, req *dto.UpdateDataReq) (res interface{}, err error) {
|
||||||
|
err = service.Data.Update(ctx, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// List 获取数据列表
|
||||||
|
func (c *cData) List(ctx context.Context, req *dto.ListDataReq) (res *dto.ListDataRes, err error) {
|
||||||
|
return service.Data.List(ctx, req)
|
||||||
|
}
|
||||||
121
dao/data_dao.go
Normal file
121
dao/data_dao.go
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
package dao
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cidService/model/dto"
|
||||||
|
"cidService/model/entity"
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"gitee.com/red-future---jilin-g/common/http"
|
||||||
|
"gitee.com/red-future---jilin-g/common/mongo"
|
||||||
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
|
"go.mongodb.org/mongo-driver/v2/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/v2/mongo/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Data = &data{}
|
||||||
|
|
||||||
|
type data struct{}
|
||||||
|
|
||||||
|
// Insert 插入数据
|
||||||
|
func (d *data) Insert(ctx context.Context, data *entity.Data) (err error) {
|
||||||
|
// 获取stream消息
|
||||||
|
redis := g.Redis()
|
||||||
|
streamMsg, err := redis.Do(ctx, "XREAD", "STREAMS", "data_stream", "$")
|
||||||
|
if err != nil {
|
||||||
|
g.Log().Errorf(ctx, "获取stream消息失败: %v", err)
|
||||||
|
} else {
|
||||||
|
g.Log().Infof(ctx, "获取到stream消息: %v", streamMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = mongo.Insert(ctx, []interface{}{data}, entity.DataCollection)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update 更新数据
|
||||||
|
func (d *data) Update(ctx context.Context, req *dto.UpdateDataReq) (err error) {
|
||||||
|
objectId, err := bson.ObjectIDFromHex(req.Id)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
filter := bson.M{"_id": objectId}
|
||||||
|
|
||||||
|
// 构建动态更新字段
|
||||||
|
updateFields := bson.M{}
|
||||||
|
if !g.IsEmpty(req.CustomerId) {
|
||||||
|
updateFields["customerId"] = req.CustomerId
|
||||||
|
}
|
||||||
|
if !g.IsEmpty(req.CustomerServiceId) {
|
||||||
|
updateFields["customerServiceId"] = req.CustomerServiceId
|
||||||
|
}
|
||||||
|
if req.IsInbound != nil {
|
||||||
|
updateFields["isInbound"] = *req.IsInbound
|
||||||
|
}
|
||||||
|
if req.IsActive != nil {
|
||||||
|
updateFields["isActive"] = *req.IsActive
|
||||||
|
}
|
||||||
|
if req.IsServed != nil {
|
||||||
|
updateFields["isServed"] = *req.IsServed
|
||||||
|
}
|
||||||
|
if req.HasSentContactCard != nil {
|
||||||
|
updateFields["hasSentContactCard"] = *req.HasSentContactCard
|
||||||
|
}
|
||||||
|
if req.HasSentNameCard != nil {
|
||||||
|
updateFields["hasSentNameCard"] = *req.HasSentNameCard
|
||||||
|
}
|
||||||
|
if req.HasLeftContactInfo != nil {
|
||||||
|
updateFields["hasLeftContactInfo"] = *req.HasLeftContactInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(updateFields) > 0 {
|
||||||
|
update := bson.M{"$set": updateFields}
|
||||||
|
_, err = mongo.Update(ctx, filter, update, entity.DataCollection)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildListFilter 构建列表查询的过滤条件
|
||||||
|
func (d *data) buildListFilter(req *dto.ListDataReq) bson.M {
|
||||||
|
filter := bson.M{}
|
||||||
|
if !g.IsEmpty(req.CustomerId) {
|
||||||
|
filter["customerId"] = req.CustomerId
|
||||||
|
}
|
||||||
|
if !g.IsEmpty(req.CustomerServiceId) {
|
||||||
|
filter["customerServiceId"] = req.CustomerServiceId
|
||||||
|
}
|
||||||
|
return filter
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkTotalCount 检查总数
|
||||||
|
func (d *data) checkTotalCount(ctx context.Context, filter bson.M) (total int64, err error) {
|
||||||
|
total, err = mongo.Count(ctx, filter, entity.DataCollection)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// List 获取数据列表
|
||||||
|
func (d *data) List(ctx context.Context, req *dto.ListDataReq) (list []*entity.Data, total int64, err error) {
|
||||||
|
// 构建查询过滤条件
|
||||||
|
filter := d.buildListFilter(req)
|
||||||
|
|
||||||
|
// 检查总数
|
||||||
|
total, err = d.checkTotalCount(ctx, filter)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页参数处理
|
||||||
|
pageNum := req.PageNum
|
||||||
|
if pageNum <= 0 {
|
||||||
|
pageNum = 1
|
||||||
|
}
|
||||||
|
pageSize := req.PageSize
|
||||||
|
if pageSize <= 0 {
|
||||||
|
pageSize = http.PageSize
|
||||||
|
}
|
||||||
|
|
||||||
|
limit := int64(pageSize)
|
||||||
|
skip := int64((pageNum - 1) * pageSize)
|
||||||
|
opts := options.Find().SetLimit(limit).SetSkip(skip).SetSort(bson.M{"sessionStartTime": -1})
|
||||||
|
|
||||||
|
err = mongo.Find(ctx, filter, &list, entity.DataCollection, opts)
|
||||||
|
return
|
||||||
|
}
|
||||||
14
go.mod
Normal file
14
go.mod
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
module cidService
|
||||||
|
|
||||||
|
go 1.25.3
|
||||||
|
|
||||||
|
require (
|
||||||
|
gitee.com/red-future---jilin-g/common v0.1.9
|
||||||
|
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.9.5
|
||||||
|
github.com/gogf/gf/contrib/nosql/redis/v2 v2.9.5
|
||||||
|
github.com/gogf/gf/v2 v2.9.5
|
||||||
|
go.mongodb.org/mongo-driver/v2 v2.4.0
|
||||||
|
golang.org/x/net v0.47.0
|
||||||
|
)
|
||||||
|
|
||||||
|
//replace gitee.com/red-future---jilin-g/common v0.1.9 => ../common
|
||||||
21
main.go
Normal file
21
main.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cidService/controller"
|
||||||
|
|
||||||
|
"gitee.com/red-future---jilin-g/common/http"
|
||||||
|
"gitee.com/red-future---jilin-g/common/jaeger"
|
||||||
|
_ "gitee.com/red-future---jilin-g/common/mongo"
|
||||||
|
_ "gitee.com/red-future---jilin-g/common/ragflow" // RAGFlow 客户端自动初始化
|
||||||
|
_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
|
||||||
|
_ "github.com/gogf/gf/contrib/nosql/redis/v2"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
defer jaeger.ShutDown(context.Background())
|
||||||
|
http.RouteRegister([]interface{}{
|
||||||
|
controller.Data,
|
||||||
|
})
|
||||||
|
select {}
|
||||||
|
}
|
||||||
67
model/dto/data_dto.go
Normal file
67
model/dto/data_dto.go
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package dto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cidService/model/entity"
|
||||||
|
|
||||||
|
"gitee.com/red-future---jilin-g/common/http"
|
||||||
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddDataReq 添加数据
|
||||||
|
type AddDataReq struct {
|
||||||
|
g.Meta `path:"/add" method:"post" tags:"数据管理" summary:"添加数据" dc:"记录客服与客户的交互数据"` // 路由: POST /data/add
|
||||||
|
CustomerId string `json:"customerId" v:"required"` // 客户ID
|
||||||
|
CustomerServiceId string `json:"customerServiceId" v:"required"` // 客服ID
|
||||||
|
CustomerServicePlatform string `json:"customerServicePlatform" v:"required"` // 客服平台
|
||||||
|
CustomerServiceName string `json:"customerServiceName" v:"required"` // 客服名称
|
||||||
|
IsInbound bool `json:"isInbound"` // 是否进线
|
||||||
|
IsActive bool `json:"isActive"` // 是否活跃
|
||||||
|
IsServed bool `json:"isServed"` // 是否接待
|
||||||
|
HasSentContactCard bool `json:"hasSentContactCard"` // 是否发联系卡
|
||||||
|
HasSentNameCard bool `json:"hasSentNameCard"` // 是否发名片
|
||||||
|
HasLeftContactInfo bool `json:"hasLeftContactInfo"` // 是否留资
|
||||||
|
SessionStartTime int64 `json:"sessionStartTime"` // 会话开始时间
|
||||||
|
MessageTime int64 `json:"messageTime"` // 消息时间
|
||||||
|
}
|
||||||
|
|
||||||
|
type AddDataRes struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateDataReq 更新数据
|
||||||
|
type UpdateDataReq struct {
|
||||||
|
g.Meta `path:"/update" method:"post" tags:"数据管理" summary:"更新数据" dc:"更新客服交互数据"` // 路由: POST /data/update
|
||||||
|
Id string `json:"id" v:"required"` // ID
|
||||||
|
CustomerId string `json:"customerId"`
|
||||||
|
CustomerServiceId string `json:"customerServiceId"`
|
||||||
|
CustomerServicePlatform string `json:"customerServicePlatform"`
|
||||||
|
CustomerServiceName string `json:"customerServiceName"`
|
||||||
|
IsInbound *bool `json:"isInbound"` // 使用指针以区分 false 和未传值
|
||||||
|
IsActive *bool `json:"isActive"`
|
||||||
|
IsServed *bool `json:"isServed"`
|
||||||
|
HasSentContactCard *bool `json:"hasSentContactCard"`
|
||||||
|
HasSentNameCard *bool `json:"hasSentNameCard"`
|
||||||
|
HasLeftContactInfo *bool `json:"hasLeftContactInfo"`
|
||||||
|
SessionStartTime int64 `json:"sessionStartTime"`
|
||||||
|
MessageTime int64 `json:"messageTime"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDataReq 获取单个数据
|
||||||
|
type GetDataReq struct {
|
||||||
|
g.Meta `path:"/one" method:"get" tags:"数据管理" summary:"获取数据详情" dc:"根据ID获取单条数据记录"` // 路由: GET /data/one
|
||||||
|
Id string `json:"id" v:"required"` // ID
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListDataReq 获取数据列表
|
||||||
|
type ListDataReq struct {
|
||||||
|
g.Meta `path:"/list" method:"get" tags:"数据管理" summary:"获取数据列表" dc:"分页查询交互数据,支持按客户、客服、时间筛选"` // 路由: GET /data/list
|
||||||
|
http.Page
|
||||||
|
CustomerId string `json:"customerId"` // 筛选:客户ID
|
||||||
|
CustomerServiceId string `json:"customerServiceId"` // 筛选:客服ID
|
||||||
|
DateRange []string `json:"dateRange"` // 筛选:时间范围 [start, end]
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListDataRes struct {
|
||||||
|
List []*entity.Data `json:"list"`
|
||||||
|
Total int `json:"total"`
|
||||||
|
}
|
||||||
25
model/entity/data.go
Normal file
25
model/entity/data.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package entity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gitee.com/red-future---jilin-g/common/do"
|
||||||
|
)
|
||||||
|
|
||||||
|
const DataCollection = "data"
|
||||||
|
|
||||||
|
type Data struct {
|
||||||
|
do.MongoBaseDO `bson:",inline"` // 嵌入基础字段:Id, Creator, CreatedAt, Updater, UpdatedAt, TenantId, IsDeleted
|
||||||
|
|
||||||
|
// 业务字段
|
||||||
|
CustomerId string `bson:"customerId" json:"customerId"` // 客户ID
|
||||||
|
CustomerServiceId string `bson:"customerServiceId" json:"customerServiceId"` // 客服ID
|
||||||
|
CustomerServicePlatform string `bson:"customerServicePlatform" json:"customerServicePlatform"` // 客服平台
|
||||||
|
CustomerServiceName string `bson:"customerServiceName" json:"customerServiceName"` // 客服名称
|
||||||
|
IsInbound bool `bson:"isInbound" json:"isInbound"` // 用户是否点开了客服页面
|
||||||
|
IsActive bool `bson:"isActive" json:"isActive"` // 用户是否开口询问
|
||||||
|
IsServed bool `bson:"isServed" json:"isServed"` // 客服是否回答了用户
|
||||||
|
HasSentContactCard bool `bson:"hasSentContactCard" json:"hasSentContactCard"` // 客服是否发送了联系卡
|
||||||
|
HasSentNameCard bool `bson:"hasSentNameCard" json:"hasSentNameCard"` // 客服是否发送了名称卡
|
||||||
|
HasLeftContactInfo bool `bson:"hasLeftContactInfo" json:"hasLeftContactInfo"` // 用户是否留下了联系信息
|
||||||
|
SessionStartTime int64 `bson:"sessionStartTime" json:"sessionStartTime"` // 业务数据的时间
|
||||||
|
MessageTime int64 `bson:"messageTime" json:"messageTime"` // 消息时间
|
||||||
|
}
|
||||||
53
service/data_service.go
Normal file
53
service/data_service.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cidService/dao"
|
||||||
|
"cidService/model/dto"
|
||||||
|
"cidService/model/entity"
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Data = new(data)
|
||||||
|
|
||||||
|
type data struct{}
|
||||||
|
|
||||||
|
// Add 添加数据
|
||||||
|
func (s *data) Add(ctx context.Context, req *dto.AddDataReq) (res *dto.AddDataRes, err error) {
|
||||||
|
data := &entity.Data{}
|
||||||
|
if err = gconv.Struct(req, data); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 设置基础字段
|
||||||
|
now := time.Now()
|
||||||
|
data.CreatedAt = now
|
||||||
|
data.UpdatedAt = now
|
||||||
|
data.IsDeleted = false
|
||||||
|
// 注意:Creator、Updater、TenantId 保持零值,不设置
|
||||||
|
|
||||||
|
if err = dao.Data.Insert(ctx, data); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
res = &dto.AddDataRes{Id: data.Id.Hex()}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update 更新数据
|
||||||
|
func (s *data) Update(ctx context.Context, req *dto.UpdateDataReq) (err error) {
|
||||||
|
return dao.Data.Update(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// List 获取数据列表
|
||||||
|
func (s *data) List(ctx context.Context, req *dto.ListDataReq) (res *dto.ListDataRes, err error) {
|
||||||
|
list, total, err := dao.Data.List(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
res = &dto.ListDataRes{
|
||||||
|
List: list,
|
||||||
|
Total: int(total),
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user