425 lines
13 KiB
Go
425 lines
13 KiB
Go
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)
|
||
}
|
||
|
||
// UpdateWxId 更新销售的登录微信 wxid
|
||
func (a *AdviceAdvicerBiz) UpdateWxId(ctx context.Context, advicerId int32, wxId string) error {
|
||
updates := map[string]interface{}{"wx_id": wxId}
|
||
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)
|
||
}
|
||
|
||
// FindByWxId 通过登录微信 wxid 查销售记录
|
||
func (a *AdviceAdvicerBiz) FindByWxId(ctx context.Context, wxId string) (model.AiAdviceAdvicer, error) {
|
||
return a.advicerImpl.FindByWxId(ctx, wxId)
|
||
}
|
||
|
||
// 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
|
||
}
|