package biz import ( "ai_scheduler/internal/biz/llm_service/third_party" "ai_scheduler/internal/data/constants" "ai_scheduler/internal/data/impl" "ai_scheduler/internal/data/model" "ai_scheduler/internal/data/mongo_model" "ai_scheduler/internal/entitys" "ai_scheduler/internal/pkg" "encoding/json" "errors" "fmt" "strings" "time" "context" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "xorm.io/builder" ) type AdviceAdvicerBiz struct { advicerImpl *impl.AdviceAdvicerImpl advicerVersionMongo *mongo_model.AdvicerVersionMongo aiAdviceModelSupImpl *impl.AiAdviceModelSupImpl adviceProjectImpl *impl.AdviceProjectImpl mongo *pkg.Mongo } func NewAdviceAdvicerBiz( advicerImpl *impl.AdviceAdvicerImpl, advicerVersionMongo *mongo_model.AdvicerVersionMongo, aiAdviceModelSupImpl *impl.AiAdviceModelSupImpl, adviceProjectImpl *impl.AdviceProjectImpl, mongo *pkg.Mongo, ) *AdviceAdvicerBiz { return &AdviceAdvicerBiz{ advicerImpl: advicerImpl, advicerVersionMongo: advicerVersionMongo, aiAdviceModelSupImpl: aiAdviceModelSupImpl, adviceProjectImpl: adviceProjectImpl, mongo: mongo, } } func (a *AdviceAdvicerBiz) Update(ctx context.Context, data *entitys.AdvicerInitReq) (int32, error) { birth, err := time.Parse("2006-01-02", data.Birth) if err != nil { return 0, err } param := &model.AiAdviceAdvicer{ AdvicerID: data.AdvicerID, ProjectID: data.ProjectID, Name: data.Name, Birth: birth, Gender: data.Gender, WorkingYears: data.WorkingYears, } // 入职时间(可选,留空则不更新) if data.JoinDate != "" { joinDate, err := time.Parse("2006-01-02", data.JoinDate) if err != nil { return 0, err } param.JoinDate = joinDate } if param.AdvicerID == 0 { err = a.advicerImpl.AddWithData(param) } else { cond := builder.NewCond() cond = cond.And(builder.Eq{"advicer_id": param.AdvicerID}) err = a.advicerImpl.UpdateByCond(&cond, param) } return param.AdvicerID, err } func (a *AdviceAdvicerBiz) List(ctx context.Context, data *entitys.AdvicerListReq) ([]map[string]interface{}, error) { cond := builder.NewCond() cond = cond.And(builder.Eq{"project_id": data.ProjectId}) list, err := a.advicerImpl.GetRange(&cond) return list, err } // Info 根据 advicerId 获取单个销售详情 func (a *AdviceAdvicerBiz) Info(ctx context.Context, data *entitys.AdvicerInfoReq) (map[string]interface{}, error) { if data.AdvicerID == 0 { return nil, errors.New("销售ID不能为空") } cond := builder.NewCond() cond = cond.And(builder.Eq{"advicer_id": data.AdvicerID}) return a.advicerImpl.GetOneBySearch(&cond) } // UpdateWxDeviceId 更新销售的微信设备 ID func (a *AdviceAdvicerBiz) UpdateWxDeviceId(ctx context.Context, advicerId int32, wxDeviceId string) error { updates := map[string]interface{}{"wx_device_id": wxDeviceId} cond := builder.NewCond() cond = cond.And(builder.Eq{"advicer_id": advicerId}) return a.advicerImpl.UpdateByCond(&cond, updates) } // HostingUpdate 更新销售的托管配置 func (a *AdviceAdvicerBiz) HostingUpdate(ctx context.Context, req *entitys.AdvicerHostingUpdateReq) error { updates := map[string]interface{}{ "hosting_enabled": req.HostingEnabled, "hosting_version_id": req.HostingVersionId, "hosting_skill_id": req.HostingSkillId, } // reply_filehelper 为可选字段,仅当显式传入时才更新 if req.ReplyFilehelper != nil { updates["reply_filehelper"] = *req.ReplyFilehelper } cond := builder.NewCond() cond = cond.And(builder.Eq{"advicer_id": req.AdvicerId}) return a.advicerImpl.UpdateByCond(&cond, updates) } // FindByWxDeviceId 通过 wx_device_id 查销售记录 func (a *AdviceAdvicerBiz) FindByWxDeviceId(ctx context.Context, wxDeviceId string) (model.AiAdviceAdvicer, error) { return a.advicerImpl.FindByWxDeviceId(ctx, wxDeviceId) } // GetLatestVersion 获取某销售的最新版本(按 lastUpdateTime 倒序取第一条) func (a *AdviceAdvicerBiz) GetLatestVersion(ctx context.Context, advicerId int32) (versionId string, version mongo_model.AdvicerVersionMongo, err error) { list, err := a.VersionList(ctx, &entitys.AdvicerVersionListReq{AdvicerId: advicerId}) if err != nil { return "", mongo_model.AdvicerVersionMongo{}, err } if len(list) == 0 { return "", mongo_model.AdvicerVersionMongo{}, fmt.Errorf("未找到该销售的版本") } // 按 LastUpdateTime 倒序找最新 latest := list[0] for _, item := range list[1:] { if item.LastUpdateTime.After(latest.LastUpdateTime) { latest = item } } return latest.Id.Hex(), mongo_model.AdvicerVersionMongo{ AdvicerId: latest.AdvicerId, VersionDesc: latest.VersionDesc, Data: latest.Data, LastUpdateTime: latest.LastUpdateTime, }, nil } func (a *AdviceAdvicerBiz) VersionAdd(ctx context.Context, param *entitys.AdvicerVersionAddReq) (id interface{}, err error) { cond := builder.NewCond() cond = cond.And(builder.Eq{"advicer_id": param.AdvicerID}) _, err = a.advicerImpl.GetOneBySearch(&cond) if err != nil { return 0, errors.New("顾问不存在") } // 构建文档:系统字段 + 维度数据展平到顶层 doc := bson.M{ "advicerId": param.AdvicerID, "versionDesc": param.VersionDesc, "lastUpdateTime": time.Now(), } for k, v := range param.Data { doc[k] = v } res, err := a.mongo.Co(a.advicerVersionMongo).InsertOne(ctx, doc) if err != nil { return nil, err } return res.InsertedID, err } func (a *AdviceAdvicerBiz) VersionUpdate(ctx context.Context, param *entitys.AdvicerVersionUpdateReq) (err error) { if len(param.Id) == 0 { return errors.New("ID不能为空") } objectID, err := primitive.ObjectIDFromHex(param.Id) if err != nil { return fmt.Errorf("ID转换失败: %w", err) } filter := bson.M{"_id": objectID} // 1. 读取现有文档,找出所有旧维度字段 var existing bson.M err = a.mongo.Co(a.advicerVersionMongo).FindOne(ctx, filter).Decode(&existing) systemKeys := map[string]bool{"_id": true, "advicerId": true, "versionDesc": true, "lastUpdateTime": true} unsetFields := bson.M{} if err == nil && existing != nil { for k := range existing { if !systemKeys[k] { unsetFields[k] = 1 } } } // 2. 构建新数据 setFields := bson.M{ "advicerId": param.AdvicerID, "versionDesc": param.VersionDesc, "lastUpdateTime": time.Now(), } for k, v := range param.Data { setFields[k] = v } // 3. 先清除旧维度字段,再写入新数据 update := bson.M{} if len(unsetFields) > 0 { update["$unset"] = unsetFields } update["$set"] = setFields _, err = a.mongo.Co(a.advicerVersionMongo).UpdateOne(ctx, filter, update) return err } func (a *AdviceAdvicerBiz) VersionList(ctx context.Context, param *entitys.AdvicerVersionListReq) (list []mongo_model.AdvicerVersionItem, err error) { filter := bson.M{} // 1. advicer_id 条件 if param.AdvicerId != 0 { filter["advicerId"] = param.AdvicerId } // 2. _id 条件 if len(param.Id) != 0 { objectID, err := primitive.ObjectIDFromHex(param.Id) if err != nil { return nil, fmt.Errorf("ID转换失败: %w", err) } filter["_id"] = objectID } // 3. version_desc 模糊查询 if len(param.VersionDesc) != 0 { // 正确的方式:指定字段名 filter["versionDesc"] = bson.M{ "$regex": primitive.Regex{ Pattern: param.VersionDesc, Options: "i", }, } } cursor, err := a.mongo.Co(a.advicerVersionMongo).Find(ctx, filter) if err != nil { return nil, err } // 遍历结果:读取原始 BSON,将非系统字段展平到 Data systemKeys := map[string]bool{"_id": true, "advicerId": true, "versionDesc": true, "lastUpdateTime": true} for cursor.Next(ctx) { var raw bson.M if err := cursor.Decode(&raw); err != nil { return nil, err } item := mongo_model.AdvicerVersionItem{} if id, ok := raw["_id"].(primitive.ObjectID); ok { item.Id = id } item.AdvicerId = int32(raw["advicerId"].(int32)) if s, ok := raw["versionDesc"].(string); ok { item.VersionDesc = s } if t, ok := raw["lastUpdateTime"].(primitive.DateTime); ok { item.LastUpdateTime = t.Time() } item.Data = make(map[string]interface{}) for k, v := range raw { if !systemKeys[k] { item.Data[k] = v } } list = append(list, item) } if err := cursor.Err(); err != nil { return nil, err } return list, err } func (a *AdviceAdvicerBiz) VersionDel(ctx context.Context, param *entitys.AdvicerVersionDelReq) (err error) { filter := bson.M{} // 1. advicer_id 条件 if len(param.Id) != 0 { objectID, err := primitive.ObjectIDFromHex(param.Id) if err != nil { return fmt.Errorf("ID转换失败: %w", err) } filter["_id"] = objectID } _, err = a.mongo.Co(a.advicerVersionMongo).DeleteOne(ctx, filter) return err } func (a *AdviceAdvicerBiz) VersionInfo(ctx context.Context, param *entitys.AdvicerVersionInfoReq) (info mongo_model.AdvicerVersionMongo, err error) { filter := bson.M{} if len(param.Id) != 0 { objectID, err := primitive.ObjectIDFromHex(param.Id) if err != nil { return info, fmt.Errorf("ID转换失败: %w", err) } filter["_id"] = objectID } var raw bson.M res := a.mongo.Co(a.advicerVersionMongo).FindOne(ctx, filter) if res.Err() != nil { return info, res.Err() } if err := res.Decode(&raw); err != nil { return info, err } systemKeys := map[string]bool{"_id": true, "advicerId": true, "versionDesc": true, "lastUpdateTime": true} if v, ok := raw["advicerId"].(int32); ok { info.AdvicerId = v } if v, ok := raw["versionDesc"].(string); ok { info.VersionDesc = v } if t, ok := raw["lastUpdateTime"].(primitive.DateTime); ok { info.LastUpdateTime = t.Time() } info.Data = make(map[string]interface{}) for k, v := range raw { if !systemKeys[k] { info.Data[k] = v } } return info, nil } func (a *AdviceAdvicerBiz) AdvicerInfo(ctx context.Context, param *entitys.AdvicerInfoReq) (info model.AiAdviceAdvicer, err error) { cond := builder.NewCond() cond = cond.And(builder.Eq{"advicer_id": param.AdvicerID}) err = a.advicerImpl.GetOneBySearchToStrut(&cond, &info) return } // VersionStyleGenerate AI 生成销售风格维度 func (a *AdviceAdvicerBiz) VersionStyleGenerate(ctx context.Context, req *entitys.AdvicerVersionStyleGenerateReq) (map[string]interface{}, error) { // 1. 查询销售信息 cond := builder.NewCond().And(builder.Eq{"advicer_id": req.AdvicerId}) var advicer model.AiAdviceAdvicer if err := a.advicerImpl.GetOneBySearchToStrut(&cond, &advicer); err != nil || advicer.AdvicerID == 0 { return nil, fmt.Errorf("销售不存在") } // 2. 查询项目信息 condP := builder.NewCond().And(builder.Eq{"project_id": advicer.ProjectID}) var project model.AiAdviceProject if err := a.adviceProjectImpl.GetOneBySearchToStrut(&condP, &project); err != nil || project.ProjectID == 0 { return nil, fmt.Errorf("项目不存在") } // 3. 确定模型配置 var sup model.AiAdviceModelSup if req.SupId > 0 { condS := builder.NewCond().And(builder.Eq{"sup_id": req.SupId}) if err := a.aiAdviceModelSupImpl.GetOneBySearchToStrut(&condS, &sup); err != nil || sup.SupID == 0 { return nil, fmt.Errorf("指定的模型配置不存在") } } else if project.ModelSupID > 0 { condS := builder.NewCond().And(builder.Eq{"sup_id": project.ModelSupID}) if err := a.aiAdviceModelSupImpl.GetOneBySearchToStrut(&condS, &sup); err != nil || sup.SupID == 0 { return nil, fmt.Errorf("项目配置的模型配置不存在") } } else { return nil, fmt.Errorf("项目未配置模型,请手动选择模型配置") } // 4. 构建提示词 prompt := constants.PromptVersionStyleGenerate prompt = strings.ReplaceAll(prompt, "{{项目名称}}", project.Name) desc := project.TemplateDesc if desc == "" { desc = project.TemplateAdvicerDesc } prompt = strings.ReplaceAll(prompt, "{{项目描述}}", desc) prompt = strings.ReplaceAll(prompt, "{{销售姓名}}", advicer.Name) prompt = strings.ReplaceAll(prompt, "{{工作年限}}", fmt.Sprintf("%d", advicer.WorkingYears)) // 用户对该销售的一句话描述 userDesc := "" if req.Desc != "" { userDesc = "\n【销售特点描述】\n" + req.Desc + "\n" } prompt = strings.ReplaceAll(prompt, "{{用户描述}}", userDesc) // 5. 调用 LLM result, err := third_party.NewOpenAi().RequestResponsesJson(ctx, sup.Key, sup.URL, sup.JSONModel, prompt) if err != nil { return nil, fmt.Errorf("AI 生成失败: %w", err) } // 6. 解析返回的 JSON var parsed map[string]interface{} if err := json.Unmarshal([]byte(result), &parsed); err != nil { return nil, fmt.Errorf("解析 AI 返回结果失败: %w", err) } // 7. 提取需要的字段(7 个维度,中文 key) resp := make(map[string]interface{}) for _, key := range []string{"方言/口音特征", "词汇偏好", "句式模式", "标点与排版习惯", "个性标签", "语气标签", "标志性对话"} { if v, ok := parsed[key]; ok { resp[key] = v } } if len(resp) == 0 { return nil, fmt.Errorf("AI 未返回有效的风格维度数据") } return resp, nil }