fix:1.调整钉钉单聊问题路由负责人整体逻辑 2.增加机器人中间回复 3. 单元测试,提示词调整
This commit is contained in:
parent
c1971e71c1
commit
c174ab683a
|
|
@ -8,6 +8,7 @@ import (
|
|||
"ai_scheduler/internal/data/constants"
|
||||
"ai_scheduler/internal/data/impl"
|
||||
"ai_scheduler/internal/data/model"
|
||||
"ai_scheduler/internal/domain/tools/common/knowledge_base"
|
||||
"ai_scheduler/internal/entitys"
|
||||
"ai_scheduler/internal/pkg"
|
||||
"ai_scheduler/internal/tools"
|
||||
|
|
@ -162,22 +163,27 @@ func (d *DingTalkBotBiz) Do(ctx context.Context, requireData *entitys.RequireDat
|
|||
// 先不接意图识别-仅提供问题处理
|
||||
func (d *DingTalkBotBiz) handleSingleChat(ctx context.Context, requireData *entitys.RequireDataDingTalkBot) (err error) {
|
||||
// 1. 获取用户信息
|
||||
requireData.UserInfo, err = d.dingTalkUser.GetUserInfoFromBot(ctx, requireData.Req.SenderStaffId, dingtalk.WithId(1))
|
||||
user, err := d.botUserImpl.GetByStaffId(requireData.Req.SenderStaffId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
requireData.ID = int32(requireData.UserInfo.UserId)
|
||||
requireData.ID = int32(user.UserID)
|
||||
requireData.UserInfo = &entitys.DingTalkUserInfo{
|
||||
UserId: int(user.UserID),
|
||||
StaffId: user.StaffID,
|
||||
Name: user.Name,
|
||||
}
|
||||
|
||||
// 2. 检查会话状态 (Redis)
|
||||
statusKey := fmt.Sprintf("ai_bot:session:status:%s", requireData.Req.SenderStaffId)
|
||||
status, _ := d.redisCli.Get(ctx, statusKey).Result()
|
||||
// statusKey := fmt.Sprintf("ai_bot:session:status:%s", requireData.Req.SenderStaffId)
|
||||
// status, _ := d.redisCli.Get(ctx, statusKey).Result()
|
||||
|
||||
if status == "WAITING_FOR_SYS_CONFIRM" {
|
||||
// 用户回复了系统名称
|
||||
sysName := requireData.Req.Text.Content
|
||||
d.redisCli.Del(ctx, statusKey)
|
||||
return d.handleWithSpecificSys(ctx, requireData, sysName)
|
||||
}
|
||||
// if status == "WAITING_FOR_SYS_CONFIRM" {
|
||||
// // 用户回复了系统名称
|
||||
// sysName := requireData.Req.Text.Content
|
||||
// d.redisCli.Del(ctx, statusKey)
|
||||
// return d.handleWithSpecificSys(ctx, requireData, sysName)
|
||||
// }
|
||||
|
||||
// 3. 获取历史记录 (最近6轮用户输入)
|
||||
userHist, err := d.getRecentUserHistory(ctx, constants.ConversationTypeSingle, requireData.ID, 6)
|
||||
|
|
@ -192,76 +198,96 @@ func (d *DingTalkBotBiz) handleSingleChat(ctx context.Context, requireData *enti
|
|||
queryText = rewrittenQuery
|
||||
}
|
||||
|
||||
// 构造识别对象
|
||||
rec := &entitys.Recognize{
|
||||
Ch: requireData.Ch,
|
||||
SystemPrompt: d.defaultPrompt(),
|
||||
UserContent: &entitys.RecognizeUserContent{
|
||||
Text: queryText,
|
||||
},
|
||||
}
|
||||
|
||||
// 5. 调用知识库
|
||||
isRetrieved, err := d.groupConfigBiz.handleKnowledge(ctx, rec, nil, requireData.Req)
|
||||
// 分类
|
||||
resolveResult, err := d.resolveSystemAndIssueType(ctx, requireData, queryText)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch resolveResult.IssueType.Code {
|
||||
case "knowledge_qa":
|
||||
// 知识库问答
|
||||
return d.handleKnowledgeQA(ctx, requireData, queryText, resolveResult)
|
||||
default: // 其他问题类型
|
||||
// 系统为空,再次询问
|
||||
if resolveResult.Sys.SysID == 0 {
|
||||
entitys.ResText(requireData.Ch, "", "\n抱歉,我无法确定您咨询的是哪个系统。请告诉我具体系统名称(如:直连天下系统、货易通系统),以便我为您准确解答或安排对应的技术支持。")
|
||||
return nil
|
||||
}
|
||||
return d.fallbackToGroupCreation(ctx, requireData, resolveResult)
|
||||
}
|
||||
}
|
||||
|
||||
// 知识库问答
|
||||
func (d *DingTalkBotBiz) handleKnowledgeQA(ctx context.Context, requireData *entitys.RequireDataDingTalkBot, queryText string, resolveResult *resolveSystemAndIssueTypeResult) error {
|
||||
// 获取租户ID
|
||||
tenantId := constants.KnowledgeTenantIdDefault
|
||||
if resolveResult.Sys.KnowlegeTenantKey != "" {
|
||||
tenantId = resolveResult.Sys.KnowlegeTenantKey
|
||||
}
|
||||
|
||||
// 获取知识库结果
|
||||
isRetrieved, err := d.getKnowledgeAnswer(ctx, requireData, tenantId, queryText)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isRetrieved {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 6. Fallback: 分类 -> 规则 -> 拉群
|
||||
return d.fallbackToGroupCreation(ctx, requireData)
|
||||
}
|
||||
|
||||
// handleWithSpecificSys 处理用户明确指定的系统
|
||||
func (d *DingTalkBotBiz) handleWithSpecificSys(ctx context.Context, requireData *entitys.RequireDataDingTalkBot, sysName string) error {
|
||||
// 1. 查找系统
|
||||
var sys model.AiSy
|
||||
cond := builder.NewCond().And(builder.Eq{"sys_name": sysName})
|
||||
err := d.sysImpl.GetOneBySearchToStrut(&cond, &sys)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
entitys.ResText(requireData.Ch, "", "抱歉,我还是没有找到名为“"+sysName+"”的系统。请联系管理员确认系统名称。")
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
// 未匹配&全局 -> 明确具体系统
|
||||
if !isRetrieved && resolveResult.Sys.SysID == 0 {
|
||||
entitys.ResText(requireData.Ch, "", "\n抱歉,知识库未命中,无法回答您的问题。\n若您的问题是某一具体系统的,请告诉我具体系统名称(如:直连天下系统、货易通系统),以便我为您准确解答。")
|
||||
return nil
|
||||
}
|
||||
// 未匹配&指定系统 -> 拉群卡片
|
||||
if !isRetrieved && resolveResult.Sys.SysID != 0 {
|
||||
entitys.ResText(requireData.Ch, "", fmt.Sprintf("\n抱歉,%s知识库未命中,无法回答您的问题。即将为您创建群聊解答。", resolveResult.Sys.SysName))
|
||||
return d.fallbackToGroupCreation(ctx, requireData, resolveResult)
|
||||
}
|
||||
|
||||
// 2. 既然已经明确了系统,直接尝试拉群(这里假设问题类型为“其他”或由LLM再次分析)
|
||||
// 为简化,这里再次调用分类逻辑,但带上已确定的系统
|
||||
return d.fallbackToGroupCreationWithSys(ctx, requireData, &sys)
|
||||
return nil
|
||||
}
|
||||
|
||||
// getRecentUserHistory 获取最近的用户输入历史
|
||||
func (d *DingTalkBotBiz) getRecentUserHistory(ctx context.Context, conversationType constants.ConversationType, id int32, limit int) ([]model.AiBotChatHi, error) {
|
||||
var his []model.AiBotChatHi
|
||||
cond := builder.NewCond().
|
||||
And(builder.Eq{"his_type": conversationType}).
|
||||
And(builder.Eq{"id": id}).
|
||||
And(builder.Eq{"role": "user"})
|
||||
|
||||
_, err := d.chatHis.GetListToStruct(&cond, &dataTemp.ReqPageBo{Limit: limit}, &his, "his_id desc")
|
||||
// 获取知识库问答结果
|
||||
func (d *DingTalkBotBiz) getKnowledgeAnswer(ctx context.Context, requireData *entitys.RequireDataDingTalkBot, tenantId string, queryText string) (bool, error) {
|
||||
// 请求知识库工具
|
||||
knowledgeBase := knowledge_base.New(d.conf.KnowledgeConfig)
|
||||
knowledgeResp, err := knowledgeBase.Query(&knowledge_base.QueryRequest{
|
||||
TenantID: tenantId, // 后续动态接参
|
||||
Query: queryText,
|
||||
Mode: constants.KnowledgeModeMix,
|
||||
Stream: true,
|
||||
Think: false,
|
||||
OnlyRAG: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return false, fmt.Errorf("请求知识库工具失败,err: %v", err)
|
||||
}
|
||||
return his, nil
|
||||
|
||||
// 读取知识库SSE数据
|
||||
return d.groupConfigBiz.readKnowledgeSSE(knowledgeResp, requireData.Ch, true)
|
||||
}
|
||||
|
||||
// fallbackToGroupCreation 分类并拉群
|
||||
func (d *DingTalkBotBiz) fallbackToGroupCreation(ctx context.Context, requireData *entitys.RequireDataDingTalkBot) error {
|
||||
type resolveSystemAndIssueTypeResult struct {
|
||||
Sys model.AiSy
|
||||
IssueType model.AiIssueType
|
||||
Classification *do.IssueClassification
|
||||
}
|
||||
|
||||
// 解析系统和问题类型
|
||||
func (d *DingTalkBotBiz) resolveSystemAndIssueType(ctx context.Context, requireData *entitys.RequireDataDingTalkBot, queryText string) (*resolveSystemAndIssueTypeResult, error) {
|
||||
// 1. 获取所有系统和问题类型用于分类
|
||||
allSys, err := d.sysImpl.FindAll()
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
sysNames := slice.Map(allSys, func(_ int, sys model.AiSy) string {
|
||||
return sys.SysName
|
||||
})
|
||||
allIssueTypes, err := d.issueImpl.IssueType.FindAll()
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
issueTypeNames := slice.Map(allIssueTypes, func(_ int, it model.AiIssueType) string {
|
||||
return it.Name
|
||||
|
|
@ -270,8 +296,7 @@ func (d *DingTalkBotBiz) fallbackToGroupCreation(ctx context.Context, requireDat
|
|||
// 2. LLM 分类
|
||||
classification, err := d.handle.ClassifyIssue(ctx, sysNames, issueTypeNames, requireData.Req.Text.Content)
|
||||
if err != nil {
|
||||
// 分类失败,使用兜底
|
||||
return d.createDefaultGroup(ctx, requireData, "系统无法识别")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 3. 匹配系统
|
||||
|
|
@ -292,68 +317,58 @@ func (d *DingTalkBotBiz) fallbackToGroupCreation(ctx context.Context, requireDat
|
|||
}
|
||||
}
|
||||
|
||||
if sys.SysID == 0 {
|
||||
|
||||
// 判断全局是否存在该规则
|
||||
_, found, _ := d.issueImpl.IssueAssignRule.FindOne(
|
||||
d.issueImpl.WithSysID(0),
|
||||
d.issueImpl.WithIssueTypeID(issueType.ID),
|
||||
d.issueImpl.WithStatus(1),
|
||||
)
|
||||
if !found {
|
||||
// 无法明确系统,且全局无该能力,询问用户
|
||||
statusKey := fmt.Sprintf("ai_bot:session:status:%s", requireData.Req.SenderStaffId)
|
||||
d.redisCli.Set(ctx, statusKey, "WAITING_FOR_SYS_CONFIRM", time.Hour)
|
||||
entitys.ResText(requireData.Ch, "", "抱歉,我无法确定您咨询的是哪个系统。请告诉我具体系统名称(如:直连天下系统、货易通系统),以便我为您安排对应的技术支持。")
|
||||
return nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return d.fallbackToGroupCreationWithSys(ctx, requireData, &sys)
|
||||
return &resolveSystemAndIssueTypeResult{
|
||||
Sys: sys,
|
||||
IssueType: issueType,
|
||||
Classification: classification,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// fallbackToGroupCreationWithSys 在已知系统的情况下进行分类并拉群
|
||||
func (d *DingTalkBotBiz) fallbackToGroupCreationWithSys(ctx context.Context, requireData *entitys.RequireDataDingTalkBot, sys *model.AiSy) error {
|
||||
// 1. 获取所有问题类型
|
||||
allIssueTypes, err := d.issueImpl.IssueType.FindAll()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
issueTypeNames := slice.Map(allIssueTypes, func(_ int, it model.AiIssueType) string {
|
||||
return it.Name
|
||||
})
|
||||
// handleWithSpecificSys 处理用户明确指定的系统
|
||||
// func (d *DingTalkBotBiz) handleWithSpecificSys(ctx context.Context, requireData *entitys.RequireDataDingTalkBot, sysName string) error {
|
||||
// // 1. 查找系统
|
||||
// var sys model.AiSy
|
||||
// cond := builder.NewCond().And(builder.Eq{"sys_name": sysName})
|
||||
// err := d.sysImpl.GetOneBySearchToStrut(&cond, &sys)
|
||||
// if err != nil {
|
||||
// if errors.Is(err, sql.ErrNoRows) {
|
||||
// entitys.ResText(requireData.Ch, "", "抱歉,我还是没有找到名为“"+sysName+"”的系统。请联系管理员确认系统名称。")
|
||||
// return nil
|
||||
// }
|
||||
// return err
|
||||
// }
|
||||
|
||||
// 2. LLM 再次分类(确定问题类型和简述)
|
||||
classification, err := d.handle.ClassifyIssue(ctx, []string{sys.SysName}, issueTypeNames, requireData.Req.Text.Content)
|
||||
if err != nil {
|
||||
return d.createDefaultGroup(ctx, requireData, "问题类型识别失败")
|
||||
}
|
||||
issueType, found, err := d.issueImpl.IssueType.FindOne(d.issueImpl.WithName(classification.IssueTypeName))
|
||||
if !found {
|
||||
log.Errorf("issue type %s not found; err: %v", classification.IssueTypeName, err)
|
||||
return fmt.Errorf("问题类型 %s 不存在", classification.IssueTypeName)
|
||||
}
|
||||
// // 2. 既然已经明确了系统,直接尝试拉群(这里假设问题类型为“其他”或由LLM再次分析)
|
||||
// // 为简化,这里再次调用分类逻辑,但带上已确定的系统
|
||||
// return d.fallbackToGroupCreationWithSys(ctx, requireData, &sys)
|
||||
// }
|
||||
|
||||
// 3. 查找分配规则
|
||||
rule, found, err := d.issueImpl.IssueAssignRule.FindOne(
|
||||
d.issueImpl.WithSysID(sys.SysID),
|
||||
d.issueImpl.WithIssueTypeID(issueType.ID),
|
||||
// getRecentUserHistory 获取最近的用户输入历史
|
||||
func (d *DingTalkBotBiz) getRecentUserHistory(ctx context.Context, conversationType constants.ConversationType, id int32, limit int) ([]model.AiBotChatHi, error) {
|
||||
var his []model.AiBotChatHi
|
||||
cond := builder.NewCond().
|
||||
And(builder.Eq{"his_type": conversationType}).
|
||||
And(builder.Eq{"id": id}).
|
||||
And(builder.Eq{"role": "user"})
|
||||
|
||||
_, err := d.chatHis.GetListToStruct(&cond, &dataTemp.ReqPageBo{Limit: limit}, &his, "his_id desc")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return his, nil
|
||||
}
|
||||
|
||||
// 在已知系统&问题类型的情况下进行分类并拉群
|
||||
func (d *DingTalkBotBiz) fallbackToGroupCreation(ctx context.Context, requireData *entitys.RequireDataDingTalkBot, resolveResult *resolveSystemAndIssueTypeResult) error {
|
||||
// 查找分配规则
|
||||
rule, found, _ := d.issueImpl.IssueAssignRule.FindOne(
|
||||
d.issueImpl.WithSysID(resolveResult.Sys.SysID),
|
||||
d.issueImpl.WithIssueTypeID(resolveResult.IssueType.ID),
|
||||
d.issueImpl.WithStatus(1),
|
||||
)
|
||||
if !found {
|
||||
// 创建默认分配规则 - 暂不考虑并发,有唯一索引
|
||||
rule = model.AiIssueAssignRule{
|
||||
SysID: sys.SysID,
|
||||
IssueTypeID: issueType.ID,
|
||||
Status: 1,
|
||||
}
|
||||
if err := d.issueImpl.IssueAssignRule.Create(&rule); err != nil {
|
||||
log.Errorf("create assign rule for sys %s and issue type %s failed; err: %v", sys.SysName, issueType.Name, err)
|
||||
return fmt.Errorf("创建分配规则 %s-%s 失败", sys.SysName, issueType.Name)
|
||||
} else {
|
||||
log.Infof("create assign rule for sys %s and issue type %s success; rule id: %d", sys.SysName, issueType.Name, rule.ID)
|
||||
}
|
||||
entitys.ResText(requireData.Ch, "", fmt.Sprintf("\n抱歉,当前系统未配置路由规则 %s-%s,请联系管理员配置。", resolveResult.Sys.SysName, resolveResult.IssueType.Name))
|
||||
return nil
|
||||
}
|
||||
|
||||
var staffIds []string
|
||||
|
|
@ -392,15 +407,15 @@ func (d *DingTalkBotBiz) fallbackToGroupCreationWithSys(ctx context.Context, req
|
|||
// 合并提问者
|
||||
staffIds = append([]string{requireData.Req.SenderStaffId}, staffIds...)
|
||||
|
||||
// 4. 发送确认卡片
|
||||
groupName := fmt.Sprintf("[%s]-%s", classification.IssueTypeName, classification.Summary)
|
||||
// 发送确认卡片
|
||||
groupName := fmt.Sprintf("[%s]-%s", resolveResult.IssueType.Name, resolveResult.Classification.Summary)
|
||||
return d.SendGroupCreationConfirmCard(ctx, &SendGroupCreationConfirmCardParams{
|
||||
RobotCode: requireData.Req.RobotCode,
|
||||
ConversationId: requireData.Req.ConversationId,
|
||||
SenderStaffId: requireData.Req.SenderStaffId,
|
||||
UserIds: staffIds,
|
||||
GroupName: groupName,
|
||||
Summary: classification.Summary,
|
||||
Summary: resolveResult.Classification.Summary,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -759,7 +774,7 @@ func (d *DingTalkBotBiz) SendGroupCreationConfirmCard(ctx context.Context, param
|
|||
}
|
||||
|
||||
// 构建卡片 OutTrackId
|
||||
outTrackId := constants.BuildCardOutTrackId(params.ConversationId, params.RobotCode)
|
||||
outTrackId := constants.BuildCardOutTrackId(params.SenderStaffId, params.RobotCode)
|
||||
|
||||
// 准备可见人员列表
|
||||
var recipients []*string
|
||||
|
|
@ -804,13 +819,13 @@ func (d *DingTalkBotBiz) SendGroupCreationConfirmCard(ctx context.Context, param
|
|||
"target_user_ids": tea.String(strings.Join(params.UserIds, ",")),
|
||||
},
|
||||
},
|
||||
ImGroupOpenSpaceModel: &card_1_0.CreateAndDeliverRequestImGroupOpenSpaceModel{
|
||||
SupportForward: tea.Bool(false),
|
||||
OpenSpaceId: tea.String("dtv1.card//IM_ROBOT." + params.SenderStaffId),
|
||||
ImRobotOpenDeliverModel: &card_1_0.CreateAndDeliverRequestImRobotOpenDeliverModel{
|
||||
SpaceType: tea.String("IM_ROBOT"),
|
||||
RobotCode: tea.String(params.RobotCode),
|
||||
},
|
||||
OpenSpaceId: tea.String("dtv1.card//im_group." + params.ConversationId),
|
||||
ImGroupOpenDeliverModel: &card_1_0.CreateAndDeliverRequestImGroupOpenDeliverModel{
|
||||
RobotCode: tea.String(params.RobotCode),
|
||||
Recipients: recipients,
|
||||
ImRobotOpenSpaceModel: &card_1_0.CreateAndDeliverRequestImRobotOpenSpaceModel{
|
||||
SupportForward: tea.Bool(false),
|
||||
},
|
||||
})
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -132,15 +132,23 @@ type IssueClassification struct {
|
|||
|
||||
// ClassifyIssue 问题分类分析
|
||||
func (r *Handle) ClassifyIssue(ctx context.Context, systems []string, issueTypes []string, userInput string) (*IssueClassification, error) {
|
||||
systemPrompt := fmt.Sprintf(`你是一个技术支持路由专家。请分析用户的输入,并将其归类到最合适的系统和问题类型中。
|
||||
systemPrompt := fmt.Sprintf(`## 角色
|
||||
你是一个技术支持路由专家。输出必须是 JSON 格式。
|
||||
## 任务
|
||||
请分析用户的输入,并将其归类到最合适的系统和问题类型中。
|
||||
- 系统名称:必须是可用系统列表中的一个,若未提及可用系统关键词,则为"全局",不要臆想!
|
||||
- 问题类型名称:必须是可用问题类型列表中的一个,若未提及可用问题类型关键词,则为空字符串
|
||||
- 问题简述:15字以内的问题简述(用于群聊命名)
|
||||
- 分类判断理由:对系统名称和问题类型名称的判断理由
|
||||
## 背景与材料:
|
||||
可用系统列表: [%s]
|
||||
可用问题类型: [%s]
|
||||
|
||||
## 输出
|
||||
请仅以 JSON 格式回复,包含以下字段:
|
||||
- sys_name: 系统名称,若未提及系统关键词,则为"全局"
|
||||
- sys_name: 系统名称
|
||||
- issue_type_name: 问题类型名称
|
||||
- summary: 15字以内的问题简述(用于群命名)
|
||||
- reason: 分类判断理由;系统名称判断理由`, strings.Join(systems, ", "), strings.Join(issueTypes, ", "))
|
||||
- summary: 问题简述
|
||||
- reason: 分类判断理由`, strings.Join(systems, ", "), strings.Join(issueTypes, ", "))
|
||||
|
||||
messages := []api.Message{
|
||||
{Role: "system", Content: systemPrompt},
|
||||
|
|
|
|||
|
|
@ -542,7 +542,7 @@ func (g *GroupConfigBiz) readKnowledgeSSE(resp io.ReadCloser, channel chan entit
|
|||
|
||||
// 知识库未命中 输出提示后中断
|
||||
if delta.XRagStatus == constants.KnowledgeRagStatusMiss {
|
||||
var missContent string = "知识库未检测到匹配信息,即将为您创建群聊解决问题。"
|
||||
var missContent string = "知识库未检测到匹配信息。"
|
||||
entitys.ResStream(channel, "", missContent)
|
||||
return false, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,8 +31,10 @@ func (k BotUserImpl) GetByStaffId(staffId string) (*model.AiBotUser, error) {
|
|||
func (k BotUserImpl) GetByUserIds(userIds []int32) ([]model.AiBotUser, error) {
|
||||
var data []model.AiBotUser
|
||||
cond := builder.NewCond()
|
||||
cond = cond.And(builder.In("user_id", userIds))
|
||||
_, err := k.GetListToStruct2(&cond, nil, &data)
|
||||
for _, userId := range userIds {
|
||||
cond = cond.Or(builder.Eq{"user_id": userId})
|
||||
}
|
||||
_, err := k.GetListToStruct(&cond, nil, &data, "user_id")
|
||||
|
||||
return data, err
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue