302 lines
8.2 KiB
Go
302 lines
8.2 KiB
Go
|
|
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
|
|||
|
|
}
|