From 78d549fa4c55fecc6bc10fb8253d0e4ff797a4de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=96=8C?= <259278618@qq.com> Date: Fri, 5 Dec 2025 14:40:33 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E9=A1=B9=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 57 ++++++++++++++++ config.yml | 40 +++++++++++ consts/errors.go | 1 + consts/redis_key.go | 1 + controller/data_controller.go | 27 ++++++++ dao/data_dao.go | 121 ++++++++++++++++++++++++++++++++++ go.mod | 14 ++++ main.go | 21 ++++++ model/dto/data_dto.go | 67 +++++++++++++++++++ model/entity/data.go | 25 +++++++ service/data_service.go | 53 +++++++++++++++ 11 files changed, 427 insertions(+) create mode 100644 Dockerfile create mode 100644 config.yml create mode 100644 consts/errors.go create mode 100644 consts/redis_key.go create mode 100644 controller/data_controller.go create mode 100644 dao/data_dao.go create mode 100644 go.mod create mode 100644 main.go create mode 100644 model/dto/data_dto.go create mode 100644 model/entity/data.go create mode 100644 service/data_service.go diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..29e6791 --- /dev/null +++ b/Dockerfile @@ -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 \ No newline at end of file diff --git a/config.yml b/config.yml new file mode 100644 index 0000000..285378f --- /dev/null +++ b/config.yml @@ -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) \ No newline at end of file diff --git a/consts/errors.go b/consts/errors.go new file mode 100644 index 0000000..d709a2b --- /dev/null +++ b/consts/errors.go @@ -0,0 +1 @@ +package consts diff --git a/consts/redis_key.go b/consts/redis_key.go new file mode 100644 index 0000000..d709a2b --- /dev/null +++ b/consts/redis_key.go @@ -0,0 +1 @@ +package consts diff --git a/controller/data_controller.go b/controller/data_controller.go new file mode 100644 index 0000000..63b2452 --- /dev/null +++ b/controller/data_controller.go @@ -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) +} diff --git a/dao/data_dao.go b/dao/data_dao.go new file mode 100644 index 0000000..ab0bbb1 --- /dev/null +++ b/dao/data_dao.go @@ -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 +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..a8fffa2 --- /dev/null +++ b/go.mod @@ -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 \ No newline at end of file diff --git a/main.go b/main.go new file mode 100644 index 0000000..9e51ea4 --- /dev/null +++ b/main.go @@ -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 {} +} diff --git a/model/dto/data_dto.go b/model/dto/data_dto.go new file mode 100644 index 0000000..f8b489b --- /dev/null +++ b/model/dto/data_dto.go @@ -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"` +} diff --git a/model/entity/data.go b/model/entity/data.go new file mode 100644 index 0000000..7760655 --- /dev/null +++ b/model/entity/data.go @@ -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"` // 消息时间 +} diff --git a/service/data_service.go b/service/data_service.go new file mode 100644 index 0000000..c3d5742 --- /dev/null +++ b/service/data_service.go @@ -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 +}