feat: 新增账号编码和HTTP连接功能
This commit is contained in:
@@ -28,7 +28,7 @@ func (d *account) Insert(ctx context.Context, req *dto.AddAccountReq) (id int64,
|
||||
}
|
||||
|
||||
func (d *account) Update(ctx context.Context, req *dto.UpdateAccountReq) (rows int64, err error) {
|
||||
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameAccount).Data(&req).Where(entity.AccountCol.Id, req.Id).Update()
|
||||
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameAccount).Data(&req).Where(entity.AccountCol.Id, req.Id).OmitEmpty().Update()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -44,7 +44,7 @@ func (d *account) Delete(ctx context.Context, req *dto.DeleteAccountReq) (rows i
|
||||
}
|
||||
|
||||
func (d *account) Count(ctx context.Context, req *dto.ListAccountReq) (count int, err error) {
|
||||
count, err = gfdb.DB(ctx).Model(ctx, public.TableNameAccount).OmitEmpty().Where(entity.AccountCol.AccountName, req.AccountName).Count()
|
||||
count, err = gfdb.DB(ctx).Model(ctx, public.TableNameAccount).OmitEmpty().Where(entity.AccountCol.AccountCode, req.AccountCode).Count()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -64,6 +64,7 @@ func (d *account) List(ctx context.Context, req *dto.ListAccountReq, fields ...s
|
||||
if !g.IsEmpty(req.Keyword) {
|
||||
model.WhereLike(entity.AccountCol.AccountName, "%"+req.Keyword+"%")
|
||||
}
|
||||
model.Where(entity.AccountCol.AccountCode, req.AccountCode)
|
||||
model.Where(entity.AccountCol.Status, req.Status)
|
||||
model.Where(entity.AccountCol.Platform, req.Platform)
|
||||
model.OrderDesc(entity.AccountCol.CreatedAt)
|
||||
@@ -79,9 +80,9 @@ func (d *account) List(ctx context.Context, req *dto.ListAccountReq, fields ...s
|
||||
return
|
||||
}
|
||||
|
||||
// GetByAccountName 根据账号名称查询客服账号(GoFrame框架原声,绕过用户信息校验)
|
||||
func (d *account) GetByAccountName(ctx context.Context, req *dto.GetByAccountNameReq, fields ...string) (res *entity.Account, err error) {
|
||||
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameAccount).NoTenantId(ctx).Where(entity.AccountCol.AccountName, req.AccountName).Fields(fields).One()
|
||||
// GetByAccountCode 根据客服账号编码查询(不带租户id)
|
||||
func (d *account) GetByAccountCode(ctx context.Context, req *dto.GetByAccountCodeReq, fields ...string) (res *entity.Account, err error) {
|
||||
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameAccount).NoTenantId(ctx).Where(entity.AccountCol.AccountCode, req.AccountCode).Fields(fields).One()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
60
dao/account_user_dialog_dao.go
Normal file
60
dao/account_user_dialog_dao.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"context"
|
||||
"customer-server/consts/public"
|
||||
"customer-server/model/dto"
|
||||
"customer-server/model/entity"
|
||||
|
||||
"gitea.com/red-future/common/db/gfdb"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
var AccountUserDialog = new(accountUserDialog)
|
||||
|
||||
type accountUserDialog struct{}
|
||||
|
||||
func (d *accountUserDialog) Insert(ctx context.Context, req *dto.AddAccountUserDialogReq) (id int64, err error) {
|
||||
var e *entity.AccountUserDialog
|
||||
if err = gconv.Struct(req, &e); err != nil {
|
||||
return
|
||||
}
|
||||
result, err := gfdb.DB(ctx).Model(ctx, public.TableNameAccountUserDialog).Insert(e)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return result.LastInsertId()
|
||||
}
|
||||
|
||||
func (d *accountUserDialog) Update(ctx context.Context, req *dto.UpdateAccountUserDialogReq) (rows int64, err error) {
|
||||
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameAccountUserDialog).Data(&req).Where(entity.AccountUserDialogCol.Id, req.Id).OmitEmpty().
|
||||
Data(entity.AccountUserDialogCol.DialogCount, &gdb.Counter{
|
||||
Field: entity.AccountUserDialogCol.DialogCount,
|
||||
Value: gconv.Float64(req.DialogCount),
|
||||
}).Update()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return r.RowsAffected()
|
||||
}
|
||||
|
||||
func (d *accountUserDialog) Delete(ctx context.Context, req *dto.DeleteAccountUserDialogReq) (rows int64, err error) {
|
||||
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameAccountUserDialog).Where(entity.AccountUserDialogCol.Id, req.Id).Delete()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return r.RowsAffected()
|
||||
}
|
||||
|
||||
func (d *accountUserDialog) Get(ctx context.Context, req *dto.GetAccountUserDialogReq) (res *entity.AccountUserDialog, err error) {
|
||||
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameAccountUserDialog).OmitEmpty().
|
||||
Where(entity.AccountUserDialogCol.UserId, req.UserId).
|
||||
Where(entity.AccountUserDialogCol.AccountId, req.AccountId).
|
||||
One()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = gconv.Struct(r, &res)
|
||||
return
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"context"
|
||||
"customer-server/model/entity"
|
||||
"time"
|
||||
|
||||
"gitea.com/red-future/common/db/mongo"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
)
|
||||
|
||||
// archive 归档 DAO
|
||||
type archive struct{}
|
||||
|
||||
// Archive 归档 DAO 单例
|
||||
var Archive = new(archive)
|
||||
|
||||
// CopyToTempByRange 将指定时间范围的数据复制到临时表
|
||||
// startTime: 开始时间(包含),endTime: 结束时间(不包含)
|
||||
func (d *archive) CopyToTempByRange(ctx context.Context, startTime, endTime time.Time) (count int64, err error) {
|
||||
db := mongo.GetDB()
|
||||
|
||||
// 查询指定时间范围的数据
|
||||
filter := bson.M{
|
||||
"createdAt": bson.M{
|
||||
"$gte": startTime,
|
||||
"$lt": endTime,
|
||||
},
|
||||
"isDeleted": false,
|
||||
}
|
||||
|
||||
cursor, err := db.Collection(entity.ConversationCollection).Find(ctx, filter)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer cursor.Close(ctx)
|
||||
|
||||
// 批量插入临时表
|
||||
batchSize := g.Cfg().MustGet(ctx, "archive.mongoBatchSize", 1000).Int()
|
||||
var docs []interface{}
|
||||
for cursor.Next(ctx) {
|
||||
var conv entity.Conversation
|
||||
if err = cursor.Decode(&conv); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 转换为临时表结构
|
||||
temp := entity.ConversationArchiveTemp{
|
||||
MongoBaseDO: conv.MongoBaseDO,
|
||||
UserId: conv.UserId,
|
||||
Platform: conv.Platform,
|
||||
SessionId: conv.SessionId,
|
||||
Question: conv.Question,
|
||||
Answer: conv.Answer,
|
||||
MessageId: conv.MessageId,
|
||||
MsgTime: conv.MsgTime,
|
||||
OriginalId: conv.Id.Hex(), // 保存原始 ID
|
||||
}
|
||||
// 清空 ID,让 MongoDB 自动生成新 ID
|
||||
temp.Id = nil
|
||||
docs = append(docs, temp)
|
||||
|
||||
// 批量插入
|
||||
if len(docs) >= batchSize {
|
||||
if _, err = db.Collection(entity.ConversationArchiveTempCollection).InsertMany(ctx, docs); err != nil {
|
||||
return
|
||||
}
|
||||
count += int64(len(docs))
|
||||
docs = docs[:0]
|
||||
}
|
||||
}
|
||||
|
||||
// 插入剩余数据
|
||||
if len(docs) > 0 {
|
||||
if _, err = db.Collection(entity.ConversationArchiveTempCollection).InsertMany(ctx, docs); err != nil {
|
||||
return
|
||||
}
|
||||
count += int64(len(docs))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// DeleteByTempIds 根据临时表中的 originalId 删除原表数据
|
||||
func (d *archive) DeleteByTempIds(ctx context.Context) (count int64, err error) {
|
||||
db := mongo.GetDB()
|
||||
|
||||
// 从临时表获取所有 originalId
|
||||
cursor, err := db.Collection(entity.ConversationArchiveTempCollection).Find(ctx, bson.M{})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer cursor.Close(ctx)
|
||||
|
||||
var ids []bson.ObjectID
|
||||
for cursor.Next(ctx) {
|
||||
var temp entity.ConversationArchiveTemp
|
||||
if err = cursor.Decode(&temp); err != nil {
|
||||
return
|
||||
}
|
||||
if oid, parseErr := bson.ObjectIDFromHex(temp.OriginalId); parseErr == nil {
|
||||
ids = append(ids, oid)
|
||||
}
|
||||
|
||||
// 每 1000 条批量删除一次
|
||||
if len(ids) >= 1000 {
|
||||
result, delErr := db.Collection(entity.ConversationCollection).DeleteMany(ctx, bson.M{
|
||||
"_id": bson.M{"$in": ids},
|
||||
})
|
||||
if delErr != nil {
|
||||
err = delErr
|
||||
return
|
||||
}
|
||||
count += result.DeletedCount
|
||||
ids = ids[:0]
|
||||
}
|
||||
}
|
||||
|
||||
// 删除剩余数据
|
||||
if len(ids) > 0 {
|
||||
result, delErr := db.Collection(entity.ConversationCollection).DeleteMany(ctx, bson.M{
|
||||
"_id": bson.M{"$in": ids},
|
||||
})
|
||||
if delErr != nil {
|
||||
err = delErr
|
||||
return
|
||||
}
|
||||
count += result.DeletedCount
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetTempData 获取临时表数据(用于写入 ES)
|
||||
func (d *archive) GetTempData(ctx context.Context) (data []*entity.ConversationArchiveTemp, err error) {
|
||||
db := mongo.GetDB()
|
||||
|
||||
cursor, err := db.Collection(entity.ConversationArchiveTempCollection).Find(ctx, bson.M{})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer cursor.Close(ctx)
|
||||
|
||||
err = cursor.All(ctx, &data)
|
||||
return
|
||||
}
|
||||
|
||||
// DropTempCollection 删除临时表
|
||||
func (d *archive) DropTempCollection(ctx context.Context) (err error) {
|
||||
return mongo.GetDB().Collection(entity.ConversationArchiveTempCollection).Drop(ctx)
|
||||
}
|
||||
|
||||
// CountTemp 统计临时表记录数
|
||||
func (d *archive) CountTemp(ctx context.Context) (count int64, err error) {
|
||||
return mongo.GetDB().Collection(entity.ConversationArchiveTempCollection).CountDocuments(ctx, bson.M{})
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"context"
|
||||
"customer-server/model/entity"
|
||||
|
||||
"gitea.com/red-future/common/db/mongo"
|
||||
|
||||
"gitea.com/red-future/common/redis"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
"go.mongodb.org/mongo-driver/v2/mongo/options"
|
||||
)
|
||||
|
||||
var Conversation = new(conversation)
|
||||
|
||||
type conversation struct{}
|
||||
|
||||
// BatchInsert 批量插入对话记录
|
||||
func (d *conversation) BatchInsert(ctx context.Context, list []*entity.Conversation) (err error) {
|
||||
if len(list) == 0 {
|
||||
return
|
||||
}
|
||||
now := gtime.Now().Time
|
||||
docs := make([]interface{}, 0, len(list))
|
||||
for _, data := range list {
|
||||
docs = append(docs, bson.M{
|
||||
"userId": data.UserId,
|
||||
"platform": data.Platform,
|
||||
"sessionId": data.SessionId,
|
||||
"question": data.Question,
|
||||
"answer": data.Answer,
|
||||
"messageId": data.MessageId,
|
||||
"msgTime": data.MsgTime,
|
||||
"tenantId": data.TenantId,
|
||||
"creator": "system",
|
||||
"createdAt": now,
|
||||
"updater": "system",
|
||||
"updatedAt": now,
|
||||
"isDeleted": false,
|
||||
})
|
||||
}
|
||||
_, err = mongo.GetDB().Collection(entity.ConversationCollection).InsertMany(ctx, docs)
|
||||
return
|
||||
}
|
||||
|
||||
// UpsertByMessageId 幂等插入对话记录(使用 message_id 做唯一键,防止重复消费)
|
||||
func (d *conversation) UpsertByMessageId(ctx context.Context, data *entity.Conversation) (inserted bool, err error) {
|
||||
filter := bson.M{"messageId": data.MessageId}
|
||||
now := gtime.Now().Time
|
||||
|
||||
update := bson.M{
|
||||
"$setOnInsert": bson.M{
|
||||
"userId": data.UserId,
|
||||
"platform": data.Platform,
|
||||
"sessionId": data.SessionId,
|
||||
"question": data.Question,
|
||||
"answer": data.Answer,
|
||||
"messageId": data.MessageId,
|
||||
"msgTime": data.MsgTime,
|
||||
"creator": "system",
|
||||
"createdAt": now,
|
||||
"tenantId": data.TenantId, // 使用传入的租户ID
|
||||
"isDeleted": false,
|
||||
},
|
||||
"$set": bson.M{
|
||||
"updater": "system",
|
||||
"updatedAt": now,
|
||||
},
|
||||
}
|
||||
|
||||
opts := options.UpdateOne().SetUpsert(true)
|
||||
result, err := mongo.GetDB().Collection(entity.ConversationCollection).UpdateOne(ctx, filter, update, opts)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// UpsertedCount > 0 表示是新插入,否则是已存在(幂等跳过)
|
||||
inserted = result.UpsertedCount > 0
|
||||
return
|
||||
}
|
||||
|
||||
// FindByUserId 根据用户ID查询对话记录
|
||||
func (d *conversation) FindByUserId(ctx context.Context, userId string, limit int64) (list []*entity.Conversation, err error) {
|
||||
filter := bson.M{"userId": userId, "isDeleted": false}
|
||||
opts := options.Find().SetSort(bson.D{{Key: "msgTime", Value: -1}}).SetLimit(limit)
|
||||
|
||||
cursor, err := mongo.GetDB().Collection(entity.ConversationCollection).Find(ctx, filter, opts)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer cursor.Close(ctx)
|
||||
|
||||
err = cursor.All(ctx, &list)
|
||||
return
|
||||
}
|
||||
|
||||
// FindBySessionId 根据 Session ID 查询对话记录
|
||||
func (d *conversation) FindBySessionId(ctx context.Context, sessionId string) (list []*entity.Conversation, err error) {
|
||||
filter := bson.M{"sessionId": sessionId, "isDeleted": false}
|
||||
opts := options.Find().SetSort(bson.D{{Key: "msgTime", Value: 1}})
|
||||
|
||||
cursor, err := mongo.GetDB().Collection(entity.ConversationCollection).Find(ctx, filter, opts)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer cursor.Close(ctx)
|
||||
|
||||
err = cursor.All(ctx, &list)
|
||||
return
|
||||
}
|
||||
|
||||
// GetRecentHistory 获取用户最近 N 轮历史对话(用于上下文注入)
|
||||
// 返回 redis.HistoryMessage 切片,按时间正序排列
|
||||
func (d *conversation) GetRecentHistory(ctx context.Context, userId string, limit int64) (history []redis.HistoryMessage, err error) {
|
||||
filter := bson.M{"userId": userId, "isDeleted": false}
|
||||
// 先按时间倒序取最近 N 条,再反转为正序
|
||||
opts := options.Find().SetSort(bson.D{{Key: "msgTime", Value: -1}}).SetLimit(limit)
|
||||
|
||||
cursor, err := mongo.GetDB().Collection(entity.ConversationCollection).Find(ctx, filter, opts)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer cursor.Close(ctx)
|
||||
|
||||
var list []*entity.Conversation
|
||||
if err = cursor.All(ctx, &list); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 反转为时间正序
|
||||
history = make([]redis.HistoryMessage, len(list))
|
||||
for i, conv := range list {
|
||||
history[len(list)-1-i] = redis.HistoryMessage{
|
||||
Question: conv.Question,
|
||||
Answer: conv.Answer,
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,197 +0,0 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"context"
|
||||
"customer-server/model/dto"
|
||||
"customer-server/model/entity"
|
||||
"fmt"
|
||||
|
||||
"gitea.com/red-future/common/beans"
|
||||
"gitea.com/red-future/common/db/mongo"
|
||||
"gitea.com/red-future/common/redis"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
)
|
||||
|
||||
var CustomerServiceAccount = new(customerServiceAccount)
|
||||
|
||||
type customerServiceAccount struct{}
|
||||
|
||||
// FindByAccountName 根据accountName查询
|
||||
// 使用 MongoDAO(不需要token验证)
|
||||
func (d *customerServiceAccount) FindByAccountName(ctx context.Context, accountName string) (account *entity.CustomerServiceAccount, err error) {
|
||||
filter := bson.M{"accountName": accountName, "isDeleted": false}
|
||||
|
||||
var result entity.CustomerServiceAccount
|
||||
err = MongoDAO.FindOne(ctx, filter, &result, entity.CustomerServiceAccountCollection)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 如果未找到记录,result 是零值
|
||||
if result.Id.IsZero() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
account = &result
|
||||
return
|
||||
}
|
||||
|
||||
// Insert 插入客服账号
|
||||
func (d *customerServiceAccount) Insert(ctx context.Context, data *entity.CustomerServiceAccount) (err error) {
|
||||
// 统一使用commonmongo.DB().Insert,自动清除缓存
|
||||
// service层已经设置了TenantId,commonmongo.DB().Insert不会覆盖已有值
|
||||
ids, err := mongo.DB().Insert(ctx, []interface{}{data}, entity.CustomerServiceAccountCollection)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(ids) > 0 {
|
||||
if oid, ok := ids[0].(bson.ObjectID); ok {
|
||||
data.Id = &oid // 取地址赋值给指针类型
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Update 更新客服账号
|
||||
func (d *customerServiceAccount) Update(ctx context.Context, req *dto.UpdateCustomerServiceAccountReq) (err error) {
|
||||
objectId, err := bson.ObjectIDFromHex(req.Id)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
filter := bson.M{"_id": objectId}
|
||||
|
||||
// 如果accountName变更,需要清理旧的缓存
|
||||
if !g.IsEmpty(req.AccountName) {
|
||||
// 先查出旧的accountName
|
||||
var oldAccount entity.CustomerServiceAccount
|
||||
if findErr := MongoDAO.FindOne(ctx, filter, &oldAccount, entity.CustomerServiceAccountCollection); findErr == nil {
|
||||
// 清理旧accountName的缓存
|
||||
oldCacheKey := fmt.Sprintf("tenant:account:%s", oldAccount.AccountName)
|
||||
redis.RedisClient().Del(ctx, oldCacheKey)
|
||||
}
|
||||
}
|
||||
|
||||
updateFields := bson.M{}
|
||||
if !g.IsEmpty(req.AccountName) {
|
||||
updateFields["accountName"] = req.AccountName
|
||||
}
|
||||
if !g.IsEmpty(req.Platform) {
|
||||
updateFields["platform"] = req.Platform
|
||||
}
|
||||
if req.SelfIdentity != nil {
|
||||
updateFields["selfIdentity"] = *req.SelfIdentity
|
||||
}
|
||||
|
||||
// 如果有字段需要更新,则执行 MongoDB 更新操作
|
||||
if len(updateFields) > 0 {
|
||||
_, err = mongo.DB().Update(ctx, filter, bson.M{"$set": updateFields}, entity.CustomerServiceAccountCollection)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Delete 软删除客服账号
|
||||
func (d *customerServiceAccount) Delete(ctx context.Context, id string) (err error) {
|
||||
objectId, err := bson.ObjectIDFromHex(id)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
filter := bson.M{"_id": objectId, "isDeleted": false}
|
||||
|
||||
// 删除前先查出accountName,清理缓存
|
||||
var account entity.CustomerServiceAccount
|
||||
if findErr := MongoDAO.FindOne(ctx, filter, &account, entity.CustomerServiceAccountCollection); findErr == nil {
|
||||
cacheKey := fmt.Sprintf("tenant:account:%s", account.AccountName)
|
||||
redis.RedisClient().Del(ctx, cacheKey)
|
||||
}
|
||||
|
||||
update := bson.M{"$set": bson.M{"isDeleted": true, "updatedAt": gtime.Now().Time}}
|
||||
_, err = mongo.DB().Update(ctx, filter, update, entity.CustomerServiceAccountCollection)
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "删除客服账号失败")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ToggleStatus 切换客服账号状态(1 启用,0 禁用)
|
||||
func (d *customerServiceAccount) ToggleStatus(ctx context.Context, req *dto.ToggleCustomerServiceAccountStatusReq) (err error) {
|
||||
objectId, err := bson.ObjectIDFromHex(req.Id)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
filter := bson.M{"_id": objectId}
|
||||
|
||||
// 先查出当前状态
|
||||
var account entity.CustomerServiceAccount
|
||||
if err = MongoDAO.FindOne(ctx, filter, &account, entity.CustomerServiceAccountCollection); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 计算新的状态:true->false,false->true(切换禁用状态)
|
||||
newIsDisabled := true
|
||||
if !account.Id.IsZero() && account.IsDisabled {
|
||||
newIsDisabled = false
|
||||
}
|
||||
|
||||
_, err = mongo.DB().Update(ctx, filter, bson.M{"$set": bson.M{"isDisabled": newIsDisabled}}, entity.CustomerServiceAccountCollection)
|
||||
return
|
||||
}
|
||||
|
||||
// buildListFilter 构建列表查询的过滤条件
|
||||
func (d *customerServiceAccount) buildListFilter(req *dto.ListCustomerServiceAccountReq) bson.M {
|
||||
filter := bson.M{}
|
||||
if !g.IsEmpty(req.AccountName) {
|
||||
filter["accountName"] = bson.M{"$regex": req.AccountName, "$options": "i"} // $regex模糊查询,忽略大小写
|
||||
}
|
||||
if req.IsDisabled != nil {
|
||||
filter["isDisabled"] = *req.IsDisabled
|
||||
}
|
||||
if !g.IsEmpty(req.Platform) {
|
||||
filter["platform"] = req.Platform
|
||||
}
|
||||
return filter
|
||||
}
|
||||
|
||||
// checkTotalCount 检查总数
|
||||
func (d *customerServiceAccount) checkTotalCount(ctx context.Context, filter bson.M) (total int64, err error) {
|
||||
total, err = mongo.DB().Count(ctx, filter, entity.CustomerServiceAccountCollection)
|
||||
return
|
||||
}
|
||||
|
||||
// List 获取客服账号列表(包含所有账号,含已禁用账号)
|
||||
func (d *customerServiceAccount) List(ctx context.Context, req *dto.ListCustomerServiceAccountReq) (list []*entity.CustomerServiceAccount, 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 = 20
|
||||
}
|
||||
|
||||
// 使用统一的mongo.DB().Find方法(支持分页和排序)
|
||||
page := &beans.Page{
|
||||
PageNum: int64(pageNum),
|
||||
PageSize: int64(pageSize),
|
||||
}
|
||||
orderBy := []beans.OrderBy{
|
||||
{Field: "createdAt", Order: beans.Desc}, // 按创建时间倒序
|
||||
}
|
||||
|
||||
_, err = mongo.DB().Find(ctx, filter, &list, entity.CustomerServiceAccountCollection, page, orderBy)
|
||||
return
|
||||
}
|
||||
167
dao/data_dao.go
167
dao/data_dao.go
@@ -1,167 +0,0 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"context"
|
||||
"customer-server/model/dto"
|
||||
"customer-server/model/entity"
|
||||
|
||||
"gitea.com/red-future/common/beans"
|
||||
"gitea.com/red-future/common/db/mongo"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
)
|
||||
|
||||
var Data = new(data)
|
||||
|
||||
type data struct{}
|
||||
|
||||
// Insert 插入数据
|
||||
func (d *data) Insert(ctx context.Context, data *entity.Data) (err error) {
|
||||
_, err = mongo.DB().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.AccountName) {
|
||||
updateFields["accountName"] = req.AccountName
|
||||
}
|
||||
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 {
|
||||
_, err = mongo.DB().Update(ctx, filter, bson.M{"$set": updateFields}, entity.DataCollection)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// // Delete 删除数据
|
||||
// func (d *data) Delete(ctx context.Context, req *dto.DeleteDataReq) (err error) {
|
||||
// objectId, err := bson.ObjectIDFromHex(req.Id)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// filter := bson.M{"_id": objectId}
|
||||
// _, err = mongo.DB().Delete(ctx, filter, d.collection)
|
||||
// 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.AccountName) {
|
||||
filter["accountName"] = *req.AccountName
|
||||
}
|
||||
|
||||
// 处理时间范围筛选
|
||||
if !g.IsEmpty(req.StartDate) || !g.IsEmpty(req.EndDate) {
|
||||
timeFilter := bson.M{}
|
||||
|
||||
// 开始日期:大于等于当天 00:00:00(时间戳秒)
|
||||
if !g.IsEmpty(req.StartDate) {
|
||||
// 将日期字符串转换为时间戳(秒)
|
||||
startTime, err := parseDate(req.StartDate)
|
||||
if err == nil {
|
||||
timeFilter["$gte"] = startTime
|
||||
}
|
||||
}
|
||||
|
||||
// 结束日期:小于等于当天 23:59:59(时间戳秒)
|
||||
if !g.IsEmpty(req.EndDate) {
|
||||
// 将日期字符串转换为时间戳(秒)+ 86399(一天的最后一秒)
|
||||
endTime, err := parseDate(req.EndDate)
|
||||
if err == nil {
|
||||
timeFilter["$lte"] = endTime + 86399 // 加上一天的秒数 - 1
|
||||
}
|
||||
}
|
||||
|
||||
if len(timeFilter) > 0 {
|
||||
filter["sessionStartTime"] = timeFilter
|
||||
}
|
||||
}
|
||||
|
||||
return filter
|
||||
}
|
||||
|
||||
// checkTotalCount 检查总数
|
||||
func (d *data) checkTotalCount(ctx context.Context, filter bson.M) (total int64, err error) {
|
||||
total, err = mongo.DB().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 = 20
|
||||
}
|
||||
|
||||
// 使用统一的mongo.DB().Find方法(支持分页和排序)
|
||||
page := &beans.Page{
|
||||
PageNum: int64(pageNum),
|
||||
PageSize: int64(pageSize),
|
||||
}
|
||||
orderBy := []beans.OrderBy{
|
||||
{Field: "sessionStartTime", Order: beans.Desc}, // 按会话开始时间倒序
|
||||
}
|
||||
|
||||
_, err = mongo.DB().Find(ctx, filter, &list, entity.DataCollection, page, orderBy)
|
||||
return
|
||||
}
|
||||
|
||||
// parseDate 将日期字符串(YYYY-MM-DD)转换为时间戳(秒)
|
||||
func parseDate(dateStr string) (int64, error) {
|
||||
// 使用 gtime 解析日期字符串
|
||||
t := gtime.NewFromStr(dateStr)
|
||||
if t == nil {
|
||||
return 0, gerror.New("日期格式错误")
|
||||
}
|
||||
// 返回时间戳(秒)
|
||||
return t.Timestamp(), nil
|
||||
}
|
||||
@@ -1,199 +0,0 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"context"
|
||||
"customer-server/model/dto"
|
||||
"customer-server/model/entity"
|
||||
|
||||
"gitea.com/red-future/common/beans"
|
||||
"gitea.com/red-future/common/db/mongo"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
)
|
||||
|
||||
// dataStatistics DAO 单例
|
||||
var DataStatistics = new(dataStatistics)
|
||||
|
||||
type dataStatistics struct{}
|
||||
|
||||
// Insert 插入数据统计
|
||||
func (d *dataStatistics) Insert(ctx context.Context, data *entity.DataStatistics) (err error) {
|
||||
// 如果 ID 为空,生成一个新的 ObjectID
|
||||
if data.Id == nil || data.Id.IsZero() {
|
||||
newId := bson.NewObjectID()
|
||||
data.Id = &newId // 取地址赋值给指针类型
|
||||
}
|
||||
|
||||
// 使用 common/db/mongo.DB().Insert,自动添加 tenantId、creator、updater 等字段
|
||||
// 确保查询时能通过 tenantId 正确过滤数据
|
||||
_, err = mongo.DB().Insert(ctx, []interface{}{data}, entity.DataStatisticsCollection)
|
||||
return
|
||||
}
|
||||
|
||||
// Update 更新数据统计
|
||||
func (d *dataStatistics) Update(ctx context.Context, req *dto.UpdateDataStatisticsReq) (err error) {
|
||||
objectId, err := bson.ObjectIDFromHex(req.Id)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
filter := bson.M{"_id": objectId}
|
||||
|
||||
updateFields := bson.M{}
|
||||
// 使用 gconv 和 gtime 转换日期
|
||||
if !g.IsEmpty(req.Date) {
|
||||
if dateTime := gtime.NewFromStr(req.Date); dateTime != nil {
|
||||
updateFields["date"] = dateTime.Time
|
||||
}
|
||||
}
|
||||
if !g.IsEmpty(req.AccountName) {
|
||||
updateFields["accountName"] = req.AccountName
|
||||
}
|
||||
if !g.IsEmpty(req.CustomerServiceName) {
|
||||
updateFields["customerServiceName"] = req.CustomerServiceName
|
||||
}
|
||||
if !g.IsEmpty(req.CustomerServicePlatform) {
|
||||
updateFields["customerServicePlatform"] = req.CustomerServicePlatform
|
||||
}
|
||||
if req.InboundCount != nil {
|
||||
updateFields["inboundCount"] = *req.InboundCount
|
||||
}
|
||||
if req.ActiveCount != nil {
|
||||
updateFields["activeCount"] = *req.ActiveCount
|
||||
}
|
||||
if req.ServedCount != nil {
|
||||
updateFields["servedCount"] = *req.ServedCount
|
||||
}
|
||||
if req.ContactCardSentCount != nil {
|
||||
updateFields["contactCardSentCount"] = *req.ContactCardSentCount
|
||||
}
|
||||
if req.NameCardSentCount != nil {
|
||||
updateFields["nameCardSentCount"] = *req.NameCardSentCount
|
||||
}
|
||||
if req.LeftContactInfoCount != nil {
|
||||
updateFields["leftContactInfoCount"] = *req.LeftContactInfoCount
|
||||
}
|
||||
if req.ResponseRate30s != nil {
|
||||
updateFields["responseRate30s"] = *req.ResponseRate30s
|
||||
}
|
||||
if req.ResponseRate60s != nil {
|
||||
updateFields["responseRate60s"] = *req.ResponseRate60s
|
||||
}
|
||||
if req.ResponseRate360s != nil {
|
||||
updateFields["responseRate360s"] = *req.ResponseRate360s
|
||||
}
|
||||
|
||||
if len(updateFields) > 0 {
|
||||
_, err = mongo.DB().Update(ctx, filter, bson.M{"$set": updateFields}, entity.DataStatisticsCollection)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// buildListFilter 构建列表查询的过滤条件
|
||||
func (d *dataStatistics) buildListFilter(req *dto.ListDataStatisticsReq) bson.M {
|
||||
filter := bson.M{}
|
||||
|
||||
// 客服平台筛选
|
||||
if !g.IsEmpty(req.CustomerServicePlatform) {
|
||||
filter["customerServicePlatform"] = req.CustomerServicePlatform
|
||||
}
|
||||
|
||||
// 日期范围筛选:支持单独传 StartDate 或 EndDate,或同时传两者
|
||||
// 前端传入字符串格式(YYYY-MM-DD),需要转换为 time.Time 进行比较
|
||||
if !g.IsEmpty(req.StartDate) || !g.IsEmpty(req.EndDate) {
|
||||
dateFilter := bson.M{}
|
||||
if !g.IsEmpty(req.StartDate) {
|
||||
// 使用 gtime 转换,设置为当天 00:00:00
|
||||
if startTime := gtime.NewFromStr(req.StartDate); startTime != nil {
|
||||
dateFilter["$gte"] = startTime.Time
|
||||
}
|
||||
}
|
||||
if !g.IsEmpty(req.EndDate) {
|
||||
// 使用 gtime 转换,设置为当天 23:59:59
|
||||
if endTime := gtime.NewFromStr(req.EndDate + " 23:59:59"); endTime != nil {
|
||||
dateFilter["$lte"] = endTime.Time
|
||||
}
|
||||
}
|
||||
filter["date"] = dateFilter
|
||||
}
|
||||
|
||||
return filter
|
||||
}
|
||||
|
||||
// checkTotalCount 检查总数
|
||||
func (d *dataStatistics) checkTotalCount(ctx context.Context, filter bson.M) (total int64, err error) {
|
||||
total, err = mongo.DB().Count(ctx, filter, entity.DataStatisticsCollection)
|
||||
return
|
||||
}
|
||||
|
||||
// List 获取数据统计列表
|
||||
func (d *dataStatistics) List(ctx context.Context, req *dto.ListDataStatisticsReq) (list []*entity.DataStatistics, 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 = 20
|
||||
}
|
||||
|
||||
// 使用统一的mongo.DB().Find方法(支持分页和排序)
|
||||
page := &beans.Page{
|
||||
PageNum: int64(pageNum),
|
||||
PageSize: int64(pageSize),
|
||||
}
|
||||
orderBy := []beans.OrderBy{
|
||||
{Field: "date", Order: beans.Desc}, // 按日期倒序
|
||||
}
|
||||
|
||||
_, err = mongo.DB().Find(ctx, filter, &list, entity.DataStatisticsCollection, page, orderBy)
|
||||
return
|
||||
}
|
||||
|
||||
// FindAllForExport 查询所有符合条件的数据统计(用于导出,不分页,需要租户过滤)
|
||||
func (d *dataStatistics) FindAllForExport(ctx context.Context, req *dto.ExportDataStatisticsReq) (list []*entity.DataStatistics, err error) {
|
||||
// 构建查询过滤条件(复用 buildListFilter 逻辑)
|
||||
filter := bson.M{}
|
||||
|
||||
// 客服平台筛选
|
||||
if !g.IsEmpty(req.CustomerServicePlatform) {
|
||||
filter["customerServicePlatform"] = req.CustomerServicePlatform
|
||||
}
|
||||
|
||||
// 日期范围筛选
|
||||
if !g.IsEmpty(req.StartDate) || !g.IsEmpty(req.EndDate) {
|
||||
dateFilter := bson.M{}
|
||||
if !g.IsEmpty(req.StartDate) {
|
||||
if startTime := gtime.NewFromStr(req.StartDate); startTime != nil {
|
||||
dateFilter["$gte"] = startTime.Time
|
||||
}
|
||||
}
|
||||
if !g.IsEmpty(req.EndDate) {
|
||||
if endTime := gtime.NewFromStr(req.EndDate + " 23:59:59"); endTime != nil {
|
||||
dateFilter["$lte"] = endTime.Time
|
||||
}
|
||||
}
|
||||
filter["date"] = dateFilter
|
||||
}
|
||||
|
||||
// 使用 mongo.DB().Find 查询,自动添加 tenantId 过滤,确保租户数据隔离
|
||||
// 不分页,设置一个足够大的PageSize
|
||||
page := &beans.Page{
|
||||
PageNum: 1,
|
||||
PageSize: 100000, // 导出场景:设置足够大的PageSize
|
||||
}
|
||||
orderBy := []beans.OrderBy{
|
||||
{Field: "date", Order: beans.Desc}, // 按日期倒序
|
||||
}
|
||||
_, err = mongo.DB().Find(ctx, filter, &list, entity.DataStatisticsCollection, page, orderBy)
|
||||
return list, err
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
commonMongo "gitea.com/red-future/common/db/mongo"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
)
|
||||
|
||||
// MongoDAO MongoDB原生查询(不需要token验证)
|
||||
var MongoDAO = new(mongoDAO)
|
||||
|
||||
type mongoDAO struct{}
|
||||
|
||||
// FindOne 原生查询单条记录(不需要token验证)
|
||||
// 未找到记录时返回 nil error,调用方需检查 result 是否为零值
|
||||
func (m *mongoDAO) FindOne(ctx context.Context, filter bson.M, result interface{}, collectionName string) error {
|
||||
db := commonMongo.GetDB()
|
||||
collection := db.Collection(collectionName)
|
||||
|
||||
err := collection.FindOne(ctx, filter).Decode(result)
|
||||
if err != nil {
|
||||
if err.Error() == "mongo: no documents in result" {
|
||||
return nil // 未找到记录,返回nil而不是错误
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// InsertOne 原生插入单条记录(不需要token验证)
|
||||
func (m *mongoDAO) InsertOne(ctx context.Context, document interface{}, collectionName string) (interface{}, error) {
|
||||
db := commonMongo.GetDB()
|
||||
collection := db.Collection(collectionName)
|
||||
|
||||
result, err := collection.InsertOne(ctx, document)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result.InsertedID, nil
|
||||
}
|
||||
|
||||
// UpdateOne 原生更新单条记录(不需要token验证)
|
||||
// 返回 matchedCount(匹配到的记录数)和 modifiedCount(实际修改的记录数)
|
||||
func (m *mongoDAO) UpdateOne(ctx context.Context, filter bson.M, update bson.M, collectionName string) (matchedCount int64, modifiedCount int64, err error) {
|
||||
db := commonMongo.GetDB()
|
||||
collection := db.Collection(collectionName)
|
||||
|
||||
result, err := collection.UpdateOne(ctx, filter, update)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
return result.MatchedCount, result.ModifiedCount, nil
|
||||
}
|
||||
|
||||
// UpdateMany 原生批量更新记录(不需要token验证)
|
||||
func (m *mongoDAO) UpdateMany(ctx context.Context, filter bson.M, update bson.M, collectionName string) (matchedCount int64, modifiedCount int64, err error) {
|
||||
db := commonMongo.GetDB()
|
||||
collection := db.Collection(collectionName)
|
||||
|
||||
result, err := collection.UpdateMany(ctx, filter, update)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
return result.MatchedCount, result.ModifiedCount, nil
|
||||
}
|
||||
|
||||
// Delete 原生删除记录(不需要token验证,用于回滚操作)
|
||||
func (m *mongoDAO) Delete(ctx context.Context, filter bson.M, collectionName string) error {
|
||||
db := commonMongo.GetDB()
|
||||
collection := db.Collection(collectionName)
|
||||
|
||||
_, err := collection.DeleteOne(ctx, filter)
|
||||
return err
|
||||
}
|
||||
@@ -1,185 +0,0 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"context"
|
||||
"customer-server/model/dto"
|
||||
"customer-server/model/entity"
|
||||
|
||||
"gitea.com/red-future/common/beans"
|
||||
"gitea.com/red-future/common/db/mongo"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
)
|
||||
|
||||
var Product = new(product)
|
||||
|
||||
type product struct{}
|
||||
|
||||
// Insert 插入产品
|
||||
// 注意:mongo.DB().Insert不会自动将生成的ID回写到原始对象
|
||||
// 必须手动从返回的InsertedIDs中提取并赋值给data.Id,否则后续访问data.Id会触发空指针异常
|
||||
func (d *product) Insert(ctx context.Context, data *entity.Product) (id bson.ObjectID, err error) {
|
||||
ids, err := mongo.DB().Insert(ctx, []interface{}{data}, entity.ProductCollection)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// 从返回的ID列表中提取ObjectID并回写到data.Id
|
||||
if len(ids) > 0 {
|
||||
if oid, ok := ids[0].(bson.ObjectID); ok {
|
||||
id = oid
|
||||
data.Id = &oid // 回写ID到原始对象,防止后续访问时空指针异常
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Update 更新产品
|
||||
func (d *product) Update(ctx context.Context, req *dto.UpdateProductReq) (err error) {
|
||||
objectId, err := bson.ObjectIDFromHex(req.Id)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
filter := bson.M{"_id": objectId, "isDeleted": false}
|
||||
|
||||
updateFields := bson.M{}
|
||||
if !g.IsEmpty(req.Name) {
|
||||
updateFields["name"] = req.Name
|
||||
}
|
||||
if !g.IsEmpty(req.Description) {
|
||||
updateFields["description"] = req.Description
|
||||
}
|
||||
// 自动更新时间为当前时间
|
||||
updateFields["updatedAt"] = gtime.Now().Time
|
||||
|
||||
if len(updateFields) > 0 {
|
||||
_, err = mongo.DB().Update(ctx, filter, bson.M{"$set": updateFields}, entity.ProductCollection)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Delete 软删除产品(设置 IsDeleted=true)
|
||||
func (d *product) Delete(ctx context.Context, req *dto.DeleteProductReq) (err error) {
|
||||
objectId, err := bson.ObjectIDFromHex(req.Id)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
filter := bson.M{"_id": objectId, "isDeleted": false}
|
||||
update := bson.M{"$set": bson.M{"isDeleted": true, "updatedAt": gtime.Now().Time}}
|
||||
_, err = mongo.DB().Update(ctx, filter, update, entity.ProductCollection)
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "删除产品失败")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// buildListFilter 构建列表查询的过滤条件
|
||||
func (d *product) buildListFilter(req *dto.ListProductReq) bson.M {
|
||||
filter := bson.M{"isDeleted": false}
|
||||
if !g.IsEmpty(req.Name) {
|
||||
filter["name"] = bson.M{"$regex": req.Name}
|
||||
}
|
||||
return filter
|
||||
}
|
||||
|
||||
// checkTotalCount 检查总数
|
||||
func (d *product) checkTotalCount(ctx context.Context, filter bson.M) (total int64, err error) {
|
||||
total, err = mongo.DB().Count(ctx, filter, entity.ProductCollection)
|
||||
return
|
||||
}
|
||||
|
||||
// FindByName 根据名称查询产品(用于去重检查)
|
||||
func (d *product) FindByName(ctx context.Context, name string) (product *entity.Product, err error) {
|
||||
filter := bson.M{
|
||||
"name": name,
|
||||
"isDeleted": false,
|
||||
}
|
||||
err = mongo.DB().FindOne(ctx, filter, &product, entity.ProductCollection)
|
||||
if err != nil {
|
||||
if err.Error() == "mongo: no documents in result" {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// List 获取产品列表(排除已删除)
|
||||
func (d *product) List(ctx context.Context, req *dto.ListProductReq) (list []*entity.Product, 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 = 20
|
||||
}
|
||||
|
||||
// 使用统一的mongo.DB().Find方法(支持分页和排序)
|
||||
page := &beans.Page{
|
||||
PageNum: int64(pageNum),
|
||||
PageSize: int64(pageSize),
|
||||
}
|
||||
orderBy := []beans.OrderBy{
|
||||
{Field: "createdAt", Order: beans.Desc}, // 按创建时间倒序
|
||||
}
|
||||
|
||||
_, err = mongo.DB().Find(ctx, filter, &list, entity.ProductCollection, page, orderBy)
|
||||
return
|
||||
}
|
||||
|
||||
// GetById 根据ID获取产品
|
||||
func (d *product) GetById(ctx context.Context, id string) (product *entity.Product, err error) {
|
||||
objectId, err := bson.ObjectIDFromHex(id)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
filter := bson.M{"_id": objectId, "isDeleted": false}
|
||||
err = mongo.DB().FindOne(ctx, filter, &product, entity.ProductCollection)
|
||||
return
|
||||
}
|
||||
|
||||
// FindAllForExport 查询所有产品用于导出(不分页)
|
||||
func (d *product) FindAllForExport(ctx context.Context, name string) (list []*entity.Product, err error) {
|
||||
filter := bson.M{}
|
||||
if !g.IsEmpty(name) {
|
||||
filter["name"] = bson.M{"$regex": name, "$options": "i"} // 模糊查询,忽略大小写
|
||||
}
|
||||
|
||||
// 使用 mongo.DB().Find(会自动过滤租户和已删除数据)
|
||||
// 导出场景:不分页,设置足够大的PageSize
|
||||
page := &beans.Page{
|
||||
PageNum: 1,
|
||||
PageSize: 100000, // 导出场景:设置足够大的PageSize
|
||||
}
|
||||
orderBy := []beans.OrderBy{
|
||||
{Field: "createdAt", Order: beans.Desc}, // 按创建时间倒序
|
||||
}
|
||||
_, err = mongo.DB().Find(ctx, filter, &list, entity.ProductCollection, page, orderBy)
|
||||
return
|
||||
}
|
||||
|
||||
// UpdateEntity 更新产品实体(用于绑定/解绑/同步等场景)
|
||||
func (d *product) UpdateEntity(ctx context.Context, product *entity.Product) (err error) {
|
||||
filter := bson.M{"_id": product.Id, "isDeleted": false}
|
||||
|
||||
// 将实体转换为bson.M
|
||||
updateDoc := bson.M{}
|
||||
data, _ := bson.Marshal(product)
|
||||
bson.Unmarshal(data, &updateDoc)
|
||||
|
||||
_, err = mongo.DB().Update(ctx, filter, bson.M{"$set": updateDoc}, entity.ProductCollection)
|
||||
return
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"context"
|
||||
"customer-server/model/entity"
|
||||
|
||||
"gitea.com/red-future/common/db/mongo"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
)
|
||||
|
||||
var RAGFlowConfig = new(ragflowConfig)
|
||||
|
||||
type ragflowConfig struct{}
|
||||
|
||||
// FindByAccountName 根据客服账号名称查询配置(带租户隔离,兼容tenantId类型不一致)
|
||||
func (d *ragflowConfig) FindByAccountName(ctx context.Context, accountName string) (*entity.RAGFlowConfig, error) {
|
||||
// 先查询客服账号获取tenantId
|
||||
account, err := CustomerServiceAccount.FindByAccountName(ctx, accountName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if account == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// 使用accountName + tenantId查询RAGFlow配置(租户隔离)
|
||||
// 先尝试原始类型查询
|
||||
filter := bson.M{"accountName": accountName, "tenantId": account.TenantId, "isDeleted": false}
|
||||
var config entity.RAGFlowConfig
|
||||
err = MongoDAO.FindOne(ctx, filter, &config, entity.RAGFlowConfigCollection)
|
||||
|
||||
// 如果未找到且tenantId可以转为string,尝试用string查询(兼容性处理)
|
||||
if (err != nil || config.Id == nil || config.Id.IsZero()) && account.TenantId != nil {
|
||||
tenantIdStr := gconv.String(account.TenantId)
|
||||
if tenantIdStr != "" {
|
||||
filter = bson.M{"accountName": accountName, "tenantId": tenantIdStr, "isDeleted": false}
|
||||
err = MongoDAO.FindOne(ctx, filter, &config, entity.RAGFlowConfigCollection)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if config.Id.IsZero() {
|
||||
return nil, nil
|
||||
}
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
// FindDatasetIdByTenant 根据租户ID查询知识库ID(从任意一条RAGFlowConfig记录中获取)
|
||||
func (d *ragflowConfig) FindDatasetIdByTenant(ctx context.Context, tenantId string) (datasetId string, err error) {
|
||||
// 先尝试字符串查询
|
||||
filter := bson.M{"tenantId": tenantId, "isDeleted": false}
|
||||
var config entity.RAGFlowConfig
|
||||
err = MongoDAO.FindOne(ctx, filter, &config, entity.RAGFlowConfigCollection)
|
||||
|
||||
// 如果未找到且tenantId可以转为数字,尝试用数字查询(兼容MongoDB中可能存储为int的情况)
|
||||
if err != nil || config.Id.IsZero() {
|
||||
tenantIdInt := gconv.Int(tenantId)
|
||||
if tenantIdInt > 0 {
|
||||
filter = bson.M{"tenantId": tenantIdInt, "isDeleted": false}
|
||||
err = MongoDAO.FindOne(ctx, filter, &config, entity.RAGFlowConfigCollection)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if config.Id.IsZero() {
|
||||
return "", nil // 未找到记录
|
||||
}
|
||||
return config.DatasetId, nil
|
||||
}
|
||||
|
||||
// Insert 插入配置
|
||||
func (d *ragflowConfig) Insert(ctx context.Context, config *entity.RAGFlowConfig) error {
|
||||
_, err := mongo.DB().Insert(ctx, []interface{}{config}, entity.RAGFlowConfigCollection)
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateEntity 更新配置(避免双重token验证冲突)
|
||||
func (d *ragflowConfig) UpdateEntity(ctx context.Context, config *entity.RAGFlowConfig) error {
|
||||
filter := bson.M{"_id": config.Id, "isDeleted": false}
|
||||
|
||||
// 将实体转换为bson.M
|
||||
updateDoc := bson.M{}
|
||||
data, _ := bson.Marshal(config)
|
||||
bson.Unmarshal(data, &updateDoc)
|
||||
|
||||
update := bson.M{"$set": updateDoc}
|
||||
// 使用MongoDAO.UpdateOne(不需要token验证)
|
||||
_, _, err := MongoDAO.UpdateOne(ctx, filter, update, entity.RAGFlowConfigCollection)
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "更新RAGFlow配置失败")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateDocumentIds 更新文档ID列表(避免双重token验证冲突)
|
||||
func (d *ragflowConfig) UpdateDocumentIds(ctx context.Context, accountName string, documentIds []string) error {
|
||||
filter := bson.M{"accountName": accountName, "isDeleted": false}
|
||||
update := bson.M{"$set": bson.M{"documentIds": documentIds}}
|
||||
// 使用MongoDAO.UpdateOne(不需要token验证)
|
||||
_, _, err := MongoDAO.UpdateOne(ctx, filter, update, entity.RAGFlowConfigCollection)
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateDatasetIdByTenant 批量更新租户的所有datasetId记录(兼容tenantId类型不一致)
|
||||
func (d *ragflowConfig) UpdateDatasetIdByTenant(ctx context.Context, tenantId, newDatasetId string) error {
|
||||
// 先尝试字符串查询
|
||||
filter := bson.M{"tenantId": tenantId, "isDeleted": false}
|
||||
update := bson.M{"$set": bson.M{"datasetId": newDatasetId}}
|
||||
matchedCount, _, err := MongoDAO.UpdateMany(ctx, filter, update, entity.RAGFlowConfigCollection)
|
||||
|
||||
// 如果未匹配到且tenantId可以转为数字,尝试用数字查询(兼容MongoDB中可能存储为int的情况)
|
||||
if (err != nil || matchedCount == 0) && gconv.Int(tenantId) > 0 {
|
||||
filter = bson.M{"tenantId": gconv.Int(tenantId), "isDeleted": false}
|
||||
matchedCount, _, err = MongoDAO.UpdateMany(ctx, filter, update, entity.RAGFlowConfigCollection)
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "批量更新datasetId失败(数字类型尝试)")
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "批量更新datasetId失败")
|
||||
}
|
||||
|
||||
if matchedCount == 0 {
|
||||
return gerror.Newf("未找到租户%s的记录", tenantId)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -27,7 +27,7 @@ func (d *scriptedSpeech) Insert(ctx context.Context, req *dto.AddScriptedSpeechR
|
||||
}
|
||||
|
||||
func (d *scriptedSpeech) Update(ctx context.Context, req *dto.UpdateScriptedSpeechReq) (rows int64, err error) {
|
||||
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameScriptedSpeech).Data(&req).Where(entity.ScriptedSpeechCol.Id, req.Id).Update()
|
||||
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameScriptedSpeech).Data(&req).Where(entity.ScriptedSpeechCol.Id, req.Id).OmitEmpty().Update()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -42,6 +42,14 @@ func (d *scriptedSpeech) Delete(ctx context.Context, req *dto.DeleteScriptedSpee
|
||||
return r.RowsAffected()
|
||||
}
|
||||
|
||||
func (d *scriptedSpeech) Count(ctx context.Context, req *dto.ListScriptedSpeechReq) (count int, err error) {
|
||||
count, err = gfdb.DB(ctx).Model(ctx, public.TableNameScriptedSpeech).OmitEmpty().
|
||||
Where(entity.ScriptedSpeechCol.DatasetId, req.DatasetId).
|
||||
Where(entity.ScriptedSpeechCol.SceneType, req.SceneType).
|
||||
Count()
|
||||
return
|
||||
}
|
||||
|
||||
// GetById 根据ID查询预制话术
|
||||
func (d *scriptedSpeech) GetById(ctx context.Context, req *dto.GetScriptedSpeechReq, fields ...string) (res *entity.ScriptedSpeech, err error) {
|
||||
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameScriptedSpeech).Where(entity.ScriptedSpeechCol.Id, req.Id).Fields(fields).One()
|
||||
@@ -52,11 +60,24 @@ func (d *scriptedSpeech) GetById(ctx context.Context, req *dto.GetScriptedSpeech
|
||||
return
|
||||
}
|
||||
|
||||
// GetByDatasetIdAndSceneType 根据数据集ID和场景类型查询预制话术
|
||||
func (d *scriptedSpeech) GetByDatasetIdAndSceneType(ctx context.Context, req *dto.ListScriptedSpeechReq, fields ...string) (res *entity.ScriptedSpeech, err error) {
|
||||
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameScriptedSpeech).Fields(fields).
|
||||
Where(entity.ScriptedSpeechCol.DatasetId, req.DatasetId).
|
||||
Where(entity.ScriptedSpeechCol.SceneType, req.SceneType).
|
||||
One()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = r.Struct(&res)
|
||||
return
|
||||
}
|
||||
|
||||
// List 获取预制话术列表
|
||||
func (d *scriptedSpeech) List(ctx context.Context, req *dto.ListScriptedSpeechReq, fields ...string) (res []*entity.ScriptedSpeech, total int, err error) {
|
||||
model := gfdb.DB(ctx).Model(ctx, public.TableNameScriptedSpeech).Fields(fields).OmitEmpty()
|
||||
model.Where(entity.ScriptedSpeechCol.AccountId, req.AccountId)
|
||||
model.Where(entity.ScriptedSpeechCol.DatasetId, req.DatasetId)
|
||||
model.Where(entity.ScriptedSpeechCol.SceneType, req.SceneType)
|
||||
model.OrderDesc(entity.ScriptedSpeechCol.CreatedAt)
|
||||
if req.Page != nil {
|
||||
model.Page(int(req.Page.PageNum), int(req.Page.PageSize))
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"context"
|
||||
"customer-server/model/entity"
|
||||
|
||||
"gitea.com/red-future/common/db/mongo"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
"go.mongodb.org/mongo-driver/v2/mongo/options"
|
||||
)
|
||||
|
||||
var Session = new(session)
|
||||
|
||||
type session struct{}
|
||||
|
||||
// Upsert 更新或插入会话(根据 userId + sessionId)
|
||||
// 注意:消费者调用,无 HTTP 上下文,直接使用原生 MongoDB 操作
|
||||
func (d *session) Upsert(ctx context.Context, data *entity.Session) (err error) {
|
||||
filter := bson.M{
|
||||
"userId": data.UserId,
|
||||
"sessionId": data.SessionId,
|
||||
"isDeleted": false,
|
||||
}
|
||||
|
||||
now := gtime.Now().Time
|
||||
update := bson.M{
|
||||
"$set": bson.M{
|
||||
"platform": data.Platform,
|
||||
"status": data.Status,
|
||||
"lastActiveAt": data.LastActiveAt,
|
||||
"updater": "system",
|
||||
"updatedAt": now,
|
||||
},
|
||||
"$inc": bson.M{
|
||||
"messageCount": 1,
|
||||
},
|
||||
"$setOnInsert": bson.M{
|
||||
"creator": "system",
|
||||
"createdAt": now,
|
||||
"isDeleted": false,
|
||||
},
|
||||
}
|
||||
|
||||
opts := options.UpdateOne().SetUpsert(true)
|
||||
_, err = mongo.GetDB().Collection(entity.SessionCollection).UpdateOne(ctx, filter, update, opts)
|
||||
return
|
||||
}
|
||||
|
||||
// Archive 归档会话
|
||||
// 注意:消费者调用,无 HTTP 上下文,直接使用原生 MongoDB 操作
|
||||
func (d *session) Archive(ctx context.Context, userId, sessionId string) (err error) {
|
||||
filter := bson.M{
|
||||
"userId": userId,
|
||||
"sessionId": sessionId,
|
||||
"isDeleted": false,
|
||||
}
|
||||
|
||||
now := gtime.Now().Time
|
||||
update := bson.M{
|
||||
"$set": bson.M{
|
||||
"status": entity.SessionStatusArchived,
|
||||
"archivedAt": now,
|
||||
"updater": "system",
|
||||
"updatedAt": now,
|
||||
},
|
||||
}
|
||||
|
||||
_, err = mongo.GetDB().Collection(entity.SessionCollection).UpdateOne(ctx, filter, update)
|
||||
return
|
||||
}
|
||||
|
||||
// FindByUserId 根据用户ID查询会话列表
|
||||
func (d *session) FindByUserId(ctx context.Context, userId string, limit int64) (list []*entity.Session, err error) {
|
||||
filter := bson.M{"userId": userId, "isDeleted": false}
|
||||
opts := options.Find().SetSort(bson.D{{Key: "lastActiveAt", Value: -1}}).SetLimit(limit)
|
||||
|
||||
cursor, err := mongo.GetDB().Collection(entity.SessionCollection).Find(ctx, filter, opts)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer cursor.Close(ctx)
|
||||
|
||||
err = cursor.All(ctx, &list)
|
||||
return
|
||||
}
|
||||
|
||||
// FindActiveByUserId 查询用户活跃会话
|
||||
func (d *session) FindActiveByUserId(ctx context.Context, userId string) (data *entity.Session, err error) {
|
||||
filter := bson.M{
|
||||
"userId": userId,
|
||||
"status": entity.SessionStatusActive,
|
||||
"isDeleted": false,
|
||||
}
|
||||
|
||||
err = mongo.GetDB().Collection(entity.SessionCollection).FindOne(ctx, filter).Decode(&data)
|
||||
return
|
||||
}
|
||||
@@ -1,301 +0,0 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"context"
|
||||
"customer-server/model/dto"
|
||||
"customer-server/model/entity"
|
||||
"strings"
|
||||
|
||||
"gitea.com/red-future/common/beans"
|
||||
"gitea.com/red-future/common/db/mongo"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/grand"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
)
|
||||
|
||||
var Speechcraft = new(speechcraft)
|
||||
|
||||
type speechcraft struct{}
|
||||
|
||||
// Insert 插入话术
|
||||
func (d *speechcraft) Insert(ctx context.Context, data *entity.Speechcraft) (id bson.ObjectID, err error) {
|
||||
// 统一使用mongo.DB().Insert,自动清除缓存
|
||||
// service层已经设置了TenantId,mongo.DB().Insert不会覆盖已有值
|
||||
ids, err := mongo.DB().Insert(ctx, []interface{}{data}, entity.SpeechcraftCollection)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(ids) > 0 {
|
||||
if oid, ok := ids[0].(bson.ObjectID); ok {
|
||||
id = oid
|
||||
data.Id = &oid // 取地址赋值给指针类型
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Update 更新话术
|
||||
func (d *speechcraft) Update(ctx context.Context, req *dto.UpdateSpeechcraftReq) (err error) {
|
||||
objectId, err := bson.ObjectIDFromHex(req.Id)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
updateFields := bson.M{}
|
||||
if !g.IsEmpty(req.Tag) {
|
||||
updateFields["tag"] = req.Tag
|
||||
}
|
||||
if !g.IsEmpty(req.Content) {
|
||||
updateFields["content"] = req.Content
|
||||
}
|
||||
// 状态机字段
|
||||
if req.Stage != nil {
|
||||
updateFields["stage"] = *req.Stage
|
||||
}
|
||||
if req.Status != nil {
|
||||
updateFields["status"] = *req.Status
|
||||
}
|
||||
if req.Keywords != nil {
|
||||
updateFields["keywords"] = req.Keywords
|
||||
}
|
||||
if req.NextStage != nil {
|
||||
updateFields["nextStage"] = *req.NextStage
|
||||
}
|
||||
if req.Platform != nil {
|
||||
updateFields["platform"] = *req.Platform
|
||||
}
|
||||
|
||||
if len(updateFields) > 0 {
|
||||
_, err = mongo.DB().Update(ctx, bson.M{"_id": objectId, "isDeleted": false}, bson.M{"$set": updateFields}, entity.SpeechcraftCollection)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Delete 软删除话术
|
||||
func (d *speechcraft) Delete(ctx context.Context, req *dto.DeleteSpeechcraftReq) (err error) {
|
||||
objectId, err := bson.ObjectIDFromHex(req.Id)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
filter := bson.M{"_id": objectId, "isDeleted": false}
|
||||
update := bson.M{"$set": bson.M{"isDeleted": true, "updatedAt": gtime.Now().Time}}
|
||||
_, err = mongo.DB().Update(ctx, filter, update, entity.SpeechcraftCollection)
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "删除话术失败")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// buildListFilter 构建列表查询的过滤条件
|
||||
func (d *speechcraft) buildListFilter(req *dto.ListSpeechcraftReq) bson.M {
|
||||
filter := bson.M{"isDeleted": false}
|
||||
if !g.IsEmpty(req.Tag) {
|
||||
filter["tag"] = bson.M{"$regex": req.Tag}
|
||||
}
|
||||
if !g.IsEmpty(req.Content) {
|
||||
filter["content"] = bson.M{"$regex": req.Content}
|
||||
}
|
||||
if req.Stage != nil {
|
||||
filter["stage"] = *req.Stage
|
||||
}
|
||||
if !g.IsEmpty(req.Platform) {
|
||||
filter["platform"] = req.Platform
|
||||
}
|
||||
return filter
|
||||
}
|
||||
|
||||
// checkTotalCount 检查总数
|
||||
func (d *speechcraft) checkTotalCount(ctx context.Context, filter bson.M) (total int64, err error) {
|
||||
total, err = mongo.DB().Count(ctx, filter, entity.SpeechcraftCollection)
|
||||
return
|
||||
}
|
||||
|
||||
// List 获取话术列表(排除已删除)
|
||||
func (d *speechcraft) List(ctx context.Context, req *dto.ListSpeechcraftReq) (list []*entity.Speechcraft, 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 = 20
|
||||
}
|
||||
|
||||
// 使用统一的mongo.DB().Find方法(支持分页和排序)
|
||||
page := &beans.Page{
|
||||
PageNum: int64(pageNum),
|
||||
PageSize: int64(pageSize),
|
||||
}
|
||||
orderBy := []beans.OrderBy{
|
||||
{Field: "createdAt", Order: beans.Desc}, // 按创建时间倒序
|
||||
}
|
||||
|
||||
_, err = mongo.DB().Find(ctx, filter, &list, entity.SpeechcraftCollection, page, orderBy)
|
||||
return
|
||||
}
|
||||
|
||||
// MatchByStage 根据阶段和用户输入匹配话术
|
||||
// 匹配逻辑:阶段匹配 + 行为匹配(可选)+ 关键字匹配(可选)
|
||||
// 从匹配结果中随机选择一条(话术池随机)
|
||||
func (d *speechcraft) MatchByStage(ctx context.Context, stage int, status, content, platform string) (script *entity.Speechcraft, err error) {
|
||||
// 查询该阶段的所有话术
|
||||
filter := bson.M{
|
||||
"stage": stage,
|
||||
"isDeleted": false,
|
||||
}
|
||||
if !g.IsEmpty(platform) {
|
||||
filter["platform"] = platform
|
||||
}
|
||||
|
||||
var list []*entity.Speechcraft
|
||||
// 使用mongo.DB().Find会自动从token或accountName获取tenantId并过滤
|
||||
// 查询所有匹配的话术(不分页)
|
||||
page := &beans.Page{
|
||||
PageNum: 1,
|
||||
PageSize: 10000, // 话术匹配场景:设置足够大的PageSize
|
||||
}
|
||||
orderBy := []beans.OrderBy{} // 无需排序,后续会随机选择
|
||||
if _, err = mongo.DB().Find(ctx, filter, &list, entity.SpeechcraftCollection, page, orderBy); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 收集所有匹配的话术
|
||||
matched := make([]*entity.Speechcraft, 0, len(list))
|
||||
for _, item := range list {
|
||||
// 行为匹配(空=任意行为都匹配)
|
||||
if !g.IsEmpty(item.Status) && item.Status != status {
|
||||
continue
|
||||
}
|
||||
|
||||
// 关键字匹配(空=任意内容都匹配)
|
||||
if len(item.Keywords) > 0 && !d.matchKeywords(content, item.Keywords) {
|
||||
continue
|
||||
}
|
||||
|
||||
// 匹配成功,加入候选池
|
||||
matched = append(matched, item)
|
||||
}
|
||||
|
||||
// 从候选池随机选择一条
|
||||
if len(matched) > 0 {
|
||||
script = matched[grand.Intn(len(matched))]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// matchKeywords 检查内容是否包含任一关键字
|
||||
func (d *speechcraft) matchKeywords(content string, keywords []string) bool {
|
||||
for _, kw := range keywords {
|
||||
if g.IsEmpty(kw) {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(content, kw) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// FindByTag 根据tag查询话术(用于去重检查,同一租户下tag唯一)
|
||||
func (d *speechcraft) FindByTag(ctx context.Context, tag string) (speechcraft *entity.Speechcraft, err error) {
|
||||
filter := bson.M{
|
||||
"tag": tag,
|
||||
"isDeleted": false,
|
||||
}
|
||||
err = mongo.DB().FindOne(ctx, filter, &speechcraft, entity.SpeechcraftCollection)
|
||||
if err != nil {
|
||||
if err.Error() == "mongo: no documents in result" {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// FindByTagAndPlatform 根据tag和platform查询话术(用于去重检查)
|
||||
func (d *speechcraft) FindByTagAndPlatform(ctx context.Context, tag, platform string) (speechcraft *entity.Speechcraft, err error) {
|
||||
filter := bson.M{
|
||||
"tag": tag,
|
||||
"platform": platform,
|
||||
"isDeleted": false,
|
||||
}
|
||||
err = mongo.DB().FindOne(ctx, filter, &speechcraft, entity.SpeechcraftCollection)
|
||||
if err != nil {
|
||||
if err.Error() == "mongo: no documents in result" {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetById 根据ID查询话术
|
||||
// 使用 MongoDAO(不需要token验证)
|
||||
func (d *speechcraft) GetById(ctx context.Context, id string) (speechcraft *entity.Speechcraft, err error) {
|
||||
objectId, err := bson.ObjectIDFromHex(id)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
filter := bson.M{"_id": objectId, "isDeleted": false}
|
||||
|
||||
var result entity.Speechcraft
|
||||
err = MongoDAO.FindOne(ctx, filter, &result, entity.SpeechcraftCollection)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 如果未找到记录,result 是零值
|
||||
if result.Id.IsZero() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
speechcraft = &result
|
||||
return
|
||||
}
|
||||
|
||||
// UpdateEntity 更新话术实体(用于绑定/解绑/同步等场景)
|
||||
func (d *speechcraft) UpdateEntity(ctx context.Context, speechcraft *entity.Speechcraft) (err error) {
|
||||
filter := bson.M{"_id": speechcraft.Id, "isDeleted": false}
|
||||
|
||||
// 将实体转换为bson.M
|
||||
updateDoc := bson.M{}
|
||||
data, _ := bson.Marshal(speechcraft)
|
||||
bson.Unmarshal(data, &updateDoc)
|
||||
|
||||
_, err = mongo.DB().Update(ctx, filter, bson.M{"$set": updateDoc}, entity.SpeechcraftCollection)
|
||||
return
|
||||
}
|
||||
|
||||
// FindByStage 查询指定阶段的所有话术
|
||||
func (d *speechcraft) FindByStage(ctx context.Context, stage int, platform string) (list []*entity.Speechcraft, err error) {
|
||||
filter := bson.M{
|
||||
"stage": stage,
|
||||
"isDeleted": false,
|
||||
}
|
||||
if !g.IsEmpty(platform) {
|
||||
filter["platform"] = platform
|
||||
}
|
||||
// 使用统一的mongo.DB().Find方法(支持分页和排序)
|
||||
page := &beans.Page{
|
||||
PageNum: 1,
|
||||
PageSize: 10000, // 查询所有话术(不分页)
|
||||
}
|
||||
orderBy := []beans.OrderBy{
|
||||
{Field: "priority", Order: beans.Desc}, // 按优先级倒序
|
||||
}
|
||||
_, err = mongo.DB().Find(ctx, filter, &list, entity.SpeechcraftCollection, page, orderBy)
|
||||
return
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"context"
|
||||
"customer-server/model/entity"
|
||||
|
||||
"gitea.com/red-future/common/db/mongo"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
"go.mongodb.org/mongo-driver/v2/mongo/options"
|
||||
)
|
||||
|
||||
var UserStage = new(userStage)
|
||||
|
||||
type userStage struct{}
|
||||
|
||||
// GetOrCreate 获取用户阶段,不存在则创建
|
||||
func (d *userStage) GetOrCreate(ctx context.Context, userId, platform string) (state *entity.UserStage, err error) {
|
||||
filter := bson.M{
|
||||
"userId": userId,
|
||||
"platform": platform,
|
||||
"isDeleted": false,
|
||||
}
|
||||
|
||||
state = &entity.UserStage{}
|
||||
if err = mongo.DB().FindOne(ctx, filter, state, entity.UserStageCollection); err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 不存在则创建
|
||||
now := gtime.Now().Time
|
||||
state = &entity.UserStage{
|
||||
UserId: userId,
|
||||
Platform: platform,
|
||||
Stage: entity.StageInit,
|
||||
Status: entity.StatusIdle,
|
||||
LastMsgAt: &now, // 取地址赋值给指针类型
|
||||
}
|
||||
state.CreatedAt = &now // 取地址赋值给指针类型
|
||||
state.UpdatedAt = &now // 取地址赋值给指针类型
|
||||
state.IsDeleted = false
|
||||
|
||||
_, err = mongo.DB().Insert(ctx, []interface{}{state}, entity.UserStageCollection)
|
||||
return
|
||||
}
|
||||
|
||||
// UpdateStage 更新用户阶段
|
||||
func (d *userStage) UpdateStage(ctx context.Context, userId, platform string, stage int) (err error) {
|
||||
filter := bson.M{
|
||||
"userId": userId,
|
||||
"platform": platform,
|
||||
"isDeleted": false,
|
||||
}
|
||||
update := bson.M{
|
||||
"$set": bson.M{
|
||||
"stage": stage,
|
||||
"lastMsgAt": gtime.Now().Time,
|
||||
"updatedAt": gtime.Now().Time,
|
||||
},
|
||||
}
|
||||
_, err = mongo.DB().Update(ctx, filter, update, entity.UserStageCollection)
|
||||
return
|
||||
}
|
||||
|
||||
// UpdateStatus 更新用户行为
|
||||
func (d *userStage) UpdateStatus(ctx context.Context, userId, platform, status string) (err error) {
|
||||
filter := bson.M{
|
||||
"userId": userId,
|
||||
"platform": platform,
|
||||
"isDeleted": false,
|
||||
}
|
||||
update := bson.M{
|
||||
"$set": bson.M{
|
||||
"status": status,
|
||||
"lastMsgAt": gtime.Now().Time,
|
||||
"updatedAt": gtime.Now().Time,
|
||||
},
|
||||
}
|
||||
_, err = mongo.DB().Update(ctx, filter, update, entity.UserStageCollection)
|
||||
return
|
||||
}
|
||||
|
||||
// UpdateStageAndStatus 同时更新阶段和行为
|
||||
func (d *userStage) UpdateStageAndStatus(ctx context.Context, userId, platform string, stage int, status string) (err error) {
|
||||
filter := bson.M{
|
||||
"userId": userId,
|
||||
"platform": platform,
|
||||
"isDeleted": false,
|
||||
}
|
||||
update := bson.M{
|
||||
"$set": bson.M{
|
||||
"stage": stage,
|
||||
"status": status,
|
||||
"lastMsgAt": gtime.Now().Time,
|
||||
"updatedAt": gtime.Now().Time,
|
||||
},
|
||||
}
|
||||
_, err = mongo.DB().Update(ctx, filter, update, entity.UserStageCollection)
|
||||
return
|
||||
}
|
||||
|
||||
// Reset 重置用户阶段到初始状态
|
||||
func (d *userStage) Reset(ctx context.Context, userId, platform string) (err error) {
|
||||
return d.UpdateStageAndStatus(ctx, userId, platform, entity.StageInit, entity.StatusIdle)
|
||||
}
|
||||
|
||||
// FindByUser 查询用户阶段
|
||||
func (d *userStage) FindByUser(ctx context.Context, userId, platform string) (state *entity.UserStage, err error) {
|
||||
filter := bson.M{
|
||||
"userId": userId,
|
||||
"platform": platform,
|
||||
"isDeleted": false,
|
||||
}
|
||||
state = &entity.UserStage{}
|
||||
err = mongo.DB().FindOne(ctx, filter, state, entity.UserStageCollection)
|
||||
return
|
||||
}
|
||||
|
||||
// Upsert 更新或插入用户阶段
|
||||
func (d *userStage) Upsert(ctx context.Context, userId, platform string, stage int, status string) (err error) {
|
||||
filter := bson.M{
|
||||
"userId": userId,
|
||||
"platform": platform,
|
||||
}
|
||||
now := gtime.Now().Time
|
||||
update := bson.M{
|
||||
"$set": bson.M{
|
||||
"stage": stage,
|
||||
"status": status,
|
||||
"lastMsgAt": now,
|
||||
"updatedAt": now,
|
||||
"isDeleted": false,
|
||||
},
|
||||
"$setOnInsert": bson.M{
|
||||
"userId": userId,
|
||||
"platform": platform,
|
||||
"createdAt": now,
|
||||
},
|
||||
}
|
||||
opts := options.UpdateOne().SetUpsert(true)
|
||||
_, err = mongo.GetDB().Collection(entity.UserStageCollection).UpdateOne(ctx, filter, update, opts)
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user