2025-12-03 13:58:37 +08:00
|
|
|
|
package redis
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
2025-12-04 17:39:31 +08:00
|
|
|
|
"context"
|
2026-01-16 13:42:15 +08:00
|
|
|
|
"errors"
|
2025-12-05 12:18:04 +08:00
|
|
|
|
"strings"
|
2025-12-09 17:55:08 +08:00
|
|
|
|
"sync"
|
2025-12-26 17:12:10 +08:00
|
|
|
|
"time"
|
2025-12-04 17:39:31 +08:00
|
|
|
|
|
2025-12-09 17:55:08 +08:00
|
|
|
|
"github.com/gogf/gf/v2/database/gredis"
|
2025-12-03 13:58:37 +08:00
|
|
|
|
"github.com/gogf/gf/v2/frame/g"
|
2025-12-06 18:04:29 +08:00
|
|
|
|
"github.com/gogf/gf/v2/os/glog"
|
2025-12-06 09:36:10 +08:00
|
|
|
|
"github.com/gogf/gf/v2/os/gtime"
|
2025-12-05 12:18:04 +08:00
|
|
|
|
"github.com/gogf/gf/v2/util/gconv"
|
2025-12-03 13:58:37 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
2025-12-09 17:55:08 +08:00
|
|
|
|
var (
|
2026-01-16 13:42:15 +08:00
|
|
|
|
// redisClient 内部使用的 Redis 客户端(单例模式)
|
2025-12-09 17:55:08 +08:00
|
|
|
|
redisClient *gredis.Redis
|
|
|
|
|
|
redisOnce sync.Once
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-01-16 13:42:15 +08:00
|
|
|
|
// getClient 获取 Redis 客户端(延迟初始化)
|
|
|
|
|
|
func getClient() *gredis.Redis {
|
2025-12-09 17:55:08 +08:00
|
|
|
|
redisOnce.Do(func() {
|
2026-01-16 13:42:15 +08:00
|
|
|
|
redisClient = g.Redis()
|
|
|
|
|
|
})
|
|
|
|
|
|
return redisClient
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-31 05:17:14 +08:00
|
|
|
|
// getClient 获取 Redis 客户端 临时方法
|
|
|
|
|
|
func GetRedisClientTest(name string) *gredis.Redis {
|
|
|
|
|
|
return g.Redis(name)
|
2026-01-16 13:42:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-28 10:17:03 +08:00
|
|
|
|
// RedisClient 获取 Redis 客户端(函数式,确保单例正确初始化)
|
|
|
|
|
|
func RedisClient() *gredis.Redis {
|
|
|
|
|
|
return getClient()
|
|
|
|
|
|
}
|
2026-01-16 13:42:15 +08:00
|
|
|
|
|
|
|
|
|
|
// Lock 分布式锁
|
|
|
|
|
|
func Lock(ctx context.Context, key string, expireSeconds int64, fn func(ctx context.Context) error) (success bool, err error) {
|
|
|
|
|
|
limit := 3
|
|
|
|
|
|
LOOP:
|
|
|
|
|
|
if limit < 0 {
|
|
|
|
|
|
return false, errors.New("锁重试次数耗尽")
|
|
|
|
|
|
}
|
|
|
|
|
|
limit--
|
2026-01-28 10:17:03 +08:00
|
|
|
|
client := getClient()
|
|
|
|
|
|
if val, err := client.Set(ctx, key, true, gredis.SetOption{
|
2026-01-16 13:42:15 +08:00
|
|
|
|
TTLOption: gredis.TTLOption{
|
|
|
|
|
|
EX: &expireSeconds,
|
|
|
|
|
|
},
|
|
|
|
|
|
NX: true,
|
|
|
|
|
|
}); err != nil {
|
|
|
|
|
|
return false, err
|
|
|
|
|
|
} else {
|
|
|
|
|
|
if val.Bool() {
|
2026-01-28 10:17:03 +08:00
|
|
|
|
defer func(client *gredis.Redis, ctx context.Context, key string) {
|
|
|
|
|
|
if _, err = client.Del(ctx, key); err != nil {
|
|
|
|
|
|
glog.Errorf(ctx, "redis client Del error: %v", err)
|
2025-12-26 17:12:10 +08:00
|
|
|
|
}
|
2026-01-28 10:17:03 +08:00
|
|
|
|
}(client, ctx, key)
|
2026-01-16 13:42:15 +08:00
|
|
|
|
if err = fn(ctx); err != nil {
|
|
|
|
|
|
return false, err
|
2025-12-26 17:35:52 +08:00
|
|
|
|
}
|
2026-01-16 13:42:15 +08:00
|
|
|
|
return true, nil
|
|
|
|
|
|
} else {
|
|
|
|
|
|
time.Sleep(time.Second)
|
|
|
|
|
|
goto LOOP
|
2025-12-26 17:12:10 +08:00
|
|
|
|
}
|
2026-01-16 13:42:15 +08:00
|
|
|
|
}
|
2025-12-26 17:12:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-29 14:46:39 +08:00
|
|
|
|
func GetReadStream(ctx context.Context, msg ...QueueMessage) error {
|
|
|
|
|
|
for _, t := range msg {
|
2025-12-31 10:46:39 +08:00
|
|
|
|
err := GetReadFromStream(ctx, t.StreamKey, t.GroupName, t.ConsumerName, t.BatchSize, t.BlockMs, t.AutoAck, t.HandleFunc)
|
2025-12-29 14:46:39 +08:00
|
|
|
|
if err != nil {
|
2025-12-31 10:46:39 +08:00
|
|
|
|
glog.Infof(ctx, "读取ReadFromStream数据失败-> 键名: %s, 消费者组: %s, 消费者名称%v\n, 失败err:%v\n", t.StreamKey, t.GroupName, t.ConsumerName, err)
|
|
|
|
|
|
continue
|
2025-12-29 14:46:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-29 14:52:31 +08:00
|
|
|
|
// GetReadFromStream 读取ReadFromStream数据
|
2025-12-31 10:46:39 +08:00
|
|
|
|
func GetReadFromStream(ctx context.Context, streamKey, groupName, consumerName string, count, blockMs int64, autoAck bool, fn func(ctx context.Context, message map[string]interface{}) error) (err error) {
|
2025-12-29 14:46:39 +08:00
|
|
|
|
glog.Infof(ctx, "初始化 Stream: %s, 消费者组: %s", streamKey, groupName)
|
|
|
|
|
|
err = InitStreamGroup(ctx, streamKey, groupName)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
for {
|
|
|
|
|
|
// 从 Redis Stream 读取一批消息
|
|
|
|
|
|
messages, err := ReadFromStream(ctx, streamKey, groupName, consumerName, count, blockMs)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
glog.Errorf(ctx, "[DEBUG Redis] XREADGROUP 错误: %v", err)
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
// 处理消息
|
|
|
|
|
|
for _, msg := range messages {
|
2025-12-31 10:46:39 +08:00
|
|
|
|
glog.Infof(ctx, "消费者 '%s' -> 接收到消息 ID: %s, 内容: %v\n", consumerName, msg.ID, msg.Values)
|
2025-12-29 14:46:39 +08:00
|
|
|
|
// 业务处理
|
|
|
|
|
|
if err = fn(ctx, msg.Values); err != nil {
|
2025-12-31 10:46:39 +08:00
|
|
|
|
glog.Infof(ctx, "业务处理失败-> err:%v\n", err)
|
|
|
|
|
|
continue
|
2025-12-29 14:46:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
// 确认消息 (ACK)
|
2025-12-31 10:46:39 +08:00
|
|
|
|
if autoAck {
|
2025-12-29 14:46:39 +08:00
|
|
|
|
// 处理成功后,必须调用 XAck,否则消息会一直留在 PEL 中
|
|
|
|
|
|
err = AckMessage(ctx, streamKey, groupName, msg.ID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
glog.Infof(ctx, "消费者 '%s' 确认消息 ID %s 失败: %v\n", consumerName, msg.ID, err)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
glog.Infof(ctx, "消费者 '%s' -> 已确认消息 ID: %s\n", consumerName, msg.ID)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2025-12-04 17:39:31 +08:00
|
|
|
|
|
2025-12-05 11:44:07 +08:00
|
|
|
|
// Stream 和消费者组常量
|
2025-12-04 17:39:31 +08:00
|
|
|
|
const (
|
2025-12-05 11:44:07 +08:00
|
|
|
|
// RAGFlow 请求 Stream Key
|
|
|
|
|
|
RAGFlowRequestStreamKey = "ragflow:request:stream"
|
2025-12-06 18:04:29 +08:00
|
|
|
|
// RAGFlow 响应 Stream Key
|
|
|
|
|
|
RAGFlowResponseStreamKey = "ragflow:response:stream"
|
|
|
|
|
|
// RAGFlow 请求消费者组名称
|
|
|
|
|
|
RAGFlowRequestConsumerGroup = "ragflow:request:consumer:group"
|
|
|
|
|
|
// RAGFlow 响应消费者组名称
|
|
|
|
|
|
RAGFlowResponseConsumerGroup = "ragflow:response:consumer:group"
|
|
|
|
|
|
// RAGFlow 消费者组名称(兼容旧代码)
|
2025-12-05 11:44:07 +08:00
|
|
|
|
RAGFlowConsumerGroup = "ragflow:consumer:group"
|
2025-12-04 17:39:31 +08:00
|
|
|
|
// 会话最后活跃时间 Key 前缀
|
|
|
|
|
|
SessionLastActiveKeyPrefix = "ragflow:session:"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-12-05 11:44:07 +08:00
|
|
|
|
// StreamMessage Redis Stream 消息结构
|
|
|
|
|
|
type StreamMessage struct {
|
|
|
|
|
|
ID string // 消息ID(自动生成)
|
|
|
|
|
|
Values map[string]interface{} // 消息内容
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// InitStreamGroup 初始化 Stream 和消费者组
|
2025-12-05 12:18:04 +08:00
|
|
|
|
// 使用 gredis Do() 方法执行 XGROUP CREATE 命令
|
2025-12-05 11:44:07 +08:00
|
|
|
|
func InitStreamGroup(ctx context.Context, streamKey, groupName string) error {
|
2025-12-05 12:18:04 +08:00
|
|
|
|
// XGROUP CREATE streamKey groupName 0 MKSTREAM
|
2026-01-28 15:27:32 +08:00
|
|
|
|
_, err := getClient().Do(ctx, "XGROUP", "CREATE", streamKey, groupName, "0", "MKSTREAM")
|
2025-12-05 11:44:07 +08:00
|
|
|
|
if err != nil {
|
2025-12-05 12:18:04 +08:00
|
|
|
|
// 如果组已存在,忽略错误
|
|
|
|
|
|
errStr := err.Error()
|
|
|
|
|
|
if strings.Contains(errStr, "BUSYGROUP") || strings.Contains(errStr, "already exists") {
|
2025-12-05 11:44:07 +08:00
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil
|
2025-12-04 17:39:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-05 11:44:07 +08:00
|
|
|
|
// AddToStream 将消息添加到 Stream
|
2025-12-05 12:18:04 +08:00
|
|
|
|
// 使用 gredis Do() 方法执行 XADD 命令
|
2025-12-09 09:20:44 +08:00
|
|
|
|
// msg 可以是结构体或 map,内部自动转换
|
|
|
|
|
|
func AddToStream(ctx context.Context, streamKey string, msg interface{}) (messageID string, err error) {
|
|
|
|
|
|
// 将结构体转换为 map
|
|
|
|
|
|
values := gconv.Map(msg)
|
|
|
|
|
|
|
2025-12-05 12:18:04 +08:00
|
|
|
|
// XADD streamKey * field1 value1 field2 value2 ...
|
2025-12-09 09:20:44 +08:00
|
|
|
|
args := make([]interface{}, 0, len(values)*2+2)
|
|
|
|
|
|
args = append(args, streamKey, "*") // "*" 自动生成ID
|
2025-12-05 12:18:04 +08:00
|
|
|
|
for key, val := range values {
|
|
|
|
|
|
args = append(args, key, val)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-28 15:27:32 +08:00
|
|
|
|
result, err := getClient().Do(ctx, "XADD", args...)
|
2025-12-04 17:39:31 +08:00
|
|
|
|
if err != nil {
|
2025-12-09 09:20:44 +08:00
|
|
|
|
return
|
2025-12-04 17:39:31 +08:00
|
|
|
|
}
|
2025-12-05 12:18:04 +08:00
|
|
|
|
|
2025-12-09 09:20:44 +08:00
|
|
|
|
messageID = result.String()
|
|
|
|
|
|
return
|
2025-12-05 11:44:07 +08:00
|
|
|
|
}
|
2025-12-04 17:39:31 +08:00
|
|
|
|
|
2025-12-10 18:02:31 +08:00
|
|
|
|
// CreateConsumerGroup 创建消费者组(如果不存在)
|
2026-01-21 10:20:32 +08:00
|
|
|
|
// XGROUP CREATE streamKey groupName 0 MKSTREAM
|
|
|
|
|
|
// 使用0作为起始ID,从Stream开头读取所有未消费消息
|
2025-12-10 18:02:31 +08:00
|
|
|
|
func CreateConsumerGroup(ctx context.Context, streamKey, groupName string) error {
|
2026-01-28 15:27:32 +08:00
|
|
|
|
_, err := getClient().Do(ctx, "XGROUP", "CREATE", streamKey, groupName, "0", "MKSTREAM")
|
2025-12-10 18:02:31 +08:00
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-05 11:44:07 +08:00
|
|
|
|
// ReadFromStream 从 Stream 读取消息(消费者组模式)
|
2025-12-05 12:18:04 +08:00
|
|
|
|
// 使用 gredis Do() 方法执行 XREADGROUP 命令
|
2025-12-05 11:44:07 +08:00
|
|
|
|
func ReadFromStream(ctx context.Context, streamKey, groupName, consumerName string, count int64, blockMs int64) ([]StreamMessage, error) {
|
2025-12-26 15:13:07 +08:00
|
|
|
|
// 检查是否需要记录trace(避免轮询产生大量trace)
|
|
|
|
|
|
execCtx := ctx
|
|
|
|
|
|
if !g.Cfg().MustGet(ctx, "jaeger.traceStream", true).Bool() {
|
|
|
|
|
|
// 不记录trace:使用background context(不继承span)
|
|
|
|
|
|
execCtx = context.Background()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-29 14:46:39 +08:00
|
|
|
|
RECONNECT:
|
2025-12-31 16:10:23 +08:00
|
|
|
|
// 先尝试读取pending消息(ID=0),处理积压
|
2026-01-28 15:27:32 +08:00
|
|
|
|
result, err := getClient().Do(execCtx,
|
2025-12-05 12:18:04 +08:00
|
|
|
|
"XREADGROUP", "GROUP", groupName, consumerName,
|
|
|
|
|
|
"COUNT", count,
|
2025-12-31 16:10:23 +08:00
|
|
|
|
"BLOCK", 0, // 不阻塞,立即返回
|
|
|
|
|
|
"STREAMS", streamKey, "0", // ID=0 读取pending消息
|
2025-12-05 12:18:04 +08:00
|
|
|
|
)
|
2025-12-05 11:44:07 +08:00
|
|
|
|
if err != nil {
|
2026-01-21 10:20:32 +08:00
|
|
|
|
g.Log().Errorf(ctx, "❌ XREADGROUP读取pending失败: stream=%s, error=%v", streamKey, err)
|
|
|
|
|
|
time.Sleep(time.Second)
|
2025-12-29 14:46:39 +08:00
|
|
|
|
goto RECONNECT
|
2025-12-04 17:39:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-21 10:20:32 +08:00
|
|
|
|
// 检查pending结果是否为空(需要检查消息数组是否为空)
|
|
|
|
|
|
hasPending := false
|
|
|
|
|
|
if result != nil && !result.IsEmpty() {
|
|
|
|
|
|
// 尝试解析map格式
|
|
|
|
|
|
if resultVal := result.Val(); resultVal != nil {
|
|
|
|
|
|
if streamsMap, ok := resultVal.(map[interface{}]interface{}); ok {
|
|
|
|
|
|
for _, streamMsgs := range streamsMap {
|
|
|
|
|
|
if msgsArray, ok := streamMsgs.([]interface{}); ok && len(msgsArray) > 0 {
|
|
|
|
|
|
hasPending = true
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-31 16:10:23 +08:00
|
|
|
|
// 如果没有pending消息,读取新消息
|
2026-01-21 10:20:32 +08:00
|
|
|
|
if !hasPending {
|
2026-01-28 15:27:32 +08:00
|
|
|
|
result, err = getClient().Do(execCtx,
|
2025-12-31 16:10:23 +08:00
|
|
|
|
"XREADGROUP", "GROUP", groupName, consumerName,
|
|
|
|
|
|
"COUNT", count,
|
|
|
|
|
|
"BLOCK", blockMs,
|
|
|
|
|
|
"STREAMS", streamKey, ">",
|
|
|
|
|
|
)
|
|
|
|
|
|
if err != nil {
|
2026-01-21 10:20:32 +08:00
|
|
|
|
g.Log().Errorf(ctx, "❌ XREADGROUP读取新消息失败: stream=%s, error=%v", streamKey, err)
|
|
|
|
|
|
time.Sleep(time.Second)
|
2025-12-31 16:10:23 +08:00
|
|
|
|
goto RECONNECT
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-06 10:13:38 +08:00
|
|
|
|
// 预分配容量,避免动态扩容
|
|
|
|
|
|
messages := make([]StreamMessage, 0, int(count))
|
2025-12-05 12:18:04 +08:00
|
|
|
|
|
2025-12-06 18:04:29 +08:00
|
|
|
|
if result == nil || result.IsEmpty() {
|
2025-12-05 12:18:04 +08:00
|
|
|
|
// 超时或没有数据
|
|
|
|
|
|
return messages, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-06 18:04:29 +08:00
|
|
|
|
// GoFrame gredis 返回格式: map[streamKey:[[msgID [field1 value1 field2 value2 ...]] ...]]
|
|
|
|
|
|
resultVal := result.Val()
|
2025-12-05 12:18:04 +08:00
|
|
|
|
|
2025-12-06 18:04:29 +08:00
|
|
|
|
// 尝试 map 格式(GoFrame gredis 返回)
|
|
|
|
|
|
if streamsMap, ok := resultVal.(map[interface{}]interface{}); ok {
|
2026-01-21 10:20:32 +08:00
|
|
|
|
for streamKey, streamMsgs := range streamsMap {
|
2025-12-06 18:04:29 +08:00
|
|
|
|
msgsArray, ok := streamMsgs.([]interface{})
|
|
|
|
|
|
if !ok {
|
2026-01-21 10:20:32 +08:00
|
|
|
|
g.Log().Errorf(ctx, "❌ streamMsgs类型转换失败: streamKey=%v, 实际类型=%T", streamKey, streamMsgs)
|
2025-12-06 18:04:29 +08:00
|
|
|
|
continue
|
|
|
|
|
|
}
|
2026-01-21 10:20:32 +08:00
|
|
|
|
for i, msgData := range msgsArray {
|
2025-12-06 18:04:29 +08:00
|
|
|
|
msgArray, ok := msgData.([]interface{})
|
2026-01-21 10:20:32 +08:00
|
|
|
|
if !ok {
|
|
|
|
|
|
g.Log().Errorf(ctx, "❌ msgData类型转换失败: index=%d, 实际类型=%T", i, msgData)
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
if len(msgArray) < 2 {
|
|
|
|
|
|
g.Log().Errorf(ctx, "❌ msgArray长度不足: index=%d, len=%d", i, len(msgArray))
|
2025-12-06 18:04:29 +08:00
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
msgID := gconv.String(msgArray[0])
|
|
|
|
|
|
fieldsArray, ok := msgArray[1].([]interface{})
|
|
|
|
|
|
if !ok {
|
2026-01-21 10:20:32 +08:00
|
|
|
|
g.Log().Errorf(ctx, "❌ fieldsArray类型转换失败: msgID=%s, msgArray[1]类型=%T", msgID, msgArray[1])
|
2025-12-06 18:04:29 +08:00
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
values := make(map[string]interface{}, len(fieldsArray)/2)
|
|
|
|
|
|
for i := 0; i < len(fieldsArray); i += 2 {
|
|
|
|
|
|
if i+1 < len(fieldsArray) {
|
|
|
|
|
|
key := gconv.String(fieldsArray[i])
|
|
|
|
|
|
values[key] = fieldsArray[i+1]
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
messages = append(messages, StreamMessage{
|
|
|
|
|
|
ID: msgID,
|
|
|
|
|
|
Values: values,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
2025-12-05 12:18:04 +08:00
|
|
|
|
}
|
2026-01-21 10:20:32 +08:00
|
|
|
|
if len(messages) == 0 {
|
|
|
|
|
|
g.Log().Errorf(ctx, "❌ [ReadFromStream] map格式解析失败: streamsMap长度=%d, 但未提取到消息", len(streamsMap))
|
|
|
|
|
|
}
|
2025-12-06 18:04:29 +08:00
|
|
|
|
return messages, nil
|
|
|
|
|
|
}
|
2025-12-05 12:18:04 +08:00
|
|
|
|
|
2025-12-06 18:04:29 +08:00
|
|
|
|
// 尝试数组格式(标准 Redis 返回)
|
|
|
|
|
|
if streamsArray, ok := resultVal.([]interface{}); ok && len(streamsArray) > 0 {
|
|
|
|
|
|
for _, streamData := range streamsArray {
|
|
|
|
|
|
streamArray, ok := streamData.([]interface{})
|
|
|
|
|
|
if !ok || len(streamArray) < 2 {
|
2025-12-05 12:18:04 +08:00
|
|
|
|
continue
|
|
|
|
|
|
}
|
2025-12-06 18:04:29 +08:00
|
|
|
|
messagesArray, ok := streamArray[1].([]interface{})
|
2025-12-05 12:18:04 +08:00
|
|
|
|
if !ok {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
2025-12-06 18:04:29 +08:00
|
|
|
|
for _, msgData := range messagesArray {
|
|
|
|
|
|
msgArray, ok := msgData.([]interface{})
|
|
|
|
|
|
if !ok || len(msgArray) < 2 {
|
|
|
|
|
|
continue
|
2025-12-05 12:18:04 +08:00
|
|
|
|
}
|
2025-12-06 18:04:29 +08:00
|
|
|
|
msgID := gconv.String(msgArray[0])
|
|
|
|
|
|
fieldsArray, ok := msgArray[1].([]interface{})
|
|
|
|
|
|
if !ok {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
values := make(map[string]interface{}, len(fieldsArray)/2)
|
|
|
|
|
|
for i := 0; i < len(fieldsArray); i += 2 {
|
|
|
|
|
|
if i+1 < len(fieldsArray) {
|
|
|
|
|
|
key := gconv.String(fieldsArray[i])
|
|
|
|
|
|
values[key] = fieldsArray[i+1]
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
messages = append(messages, StreamMessage{
|
|
|
|
|
|
ID: msgID,
|
|
|
|
|
|
Values: values,
|
|
|
|
|
|
})
|
2025-12-05 12:18:04 +08:00
|
|
|
|
}
|
2025-12-05 11:44:07 +08:00
|
|
|
|
}
|
2026-01-21 10:20:32 +08:00
|
|
|
|
if len(messages) == 0 {
|
|
|
|
|
|
g.Log().Errorf(ctx, "❌ [ReadFromStream] 数组格式解析失败: streamsArray长度=%d, 但未提取到消息", len(streamsArray))
|
|
|
|
|
|
}
|
|
|
|
|
|
return messages, nil
|
2025-12-04 17:39:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-21 10:20:32 +08:00
|
|
|
|
g.Log().Errorf(ctx, "❌ [ReadFromStream] 无法识别的result格式, resultVal类型: %T, 值: %+v", resultVal, resultVal)
|
2025-12-05 11:44:07 +08:00
|
|
|
|
return messages, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// AckMessage 确认消息已处理
|
2025-12-05 12:18:04 +08:00
|
|
|
|
// 使用 gredis Do() 方法执行 XACK 命令
|
2025-12-05 11:44:07 +08:00
|
|
|
|
func AckMessage(ctx context.Context, streamKey, groupName string, messageIDs ...string) error {
|
2025-12-05 12:18:04 +08:00
|
|
|
|
// XACK streamKey groupName messageID1 messageID2 ...
|
2025-12-06 10:13:38 +08:00
|
|
|
|
// 预分配容量,避免动态扩容
|
|
|
|
|
|
args := make([]interface{}, 0, len(messageIDs)+2)
|
|
|
|
|
|
args = append(args, streamKey, groupName)
|
2025-12-05 12:18:04 +08:00
|
|
|
|
for _, id := range messageIDs {
|
|
|
|
|
|
args = append(args, id)
|
2025-12-05 11:44:07 +08:00
|
|
|
|
}
|
2025-12-05 12:18:04 +08:00
|
|
|
|
|
2026-01-28 15:27:32 +08:00
|
|
|
|
_, err := getClient().Do(ctx, "XACK", args...)
|
2025-12-05 12:18:04 +08:00
|
|
|
|
return err
|
2025-12-04 17:39:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-05 11:44:07 +08:00
|
|
|
|
// GetStreamLength 获取 Stream 当前长度
|
2025-12-05 12:18:04 +08:00
|
|
|
|
// 使用 gredis Do() 方法执行 XLEN 命令
|
2025-12-05 11:44:07 +08:00
|
|
|
|
func GetStreamLength(ctx context.Context, streamKey string) (int64, error) {
|
2025-12-05 12:18:04 +08:00
|
|
|
|
// XLEN streamKey
|
2026-01-28 15:27:32 +08:00
|
|
|
|
result, err := getClient().Do(ctx, "XLEN", streamKey)
|
2025-12-04 17:39:31 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return 0, err
|
|
|
|
|
|
}
|
2025-12-05 12:18:04 +08:00
|
|
|
|
|
|
|
|
|
|
length := gconv.Int64(result)
|
2025-12-05 11:44:07 +08:00
|
|
|
|
return length, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-05 12:18:04 +08:00
|
|
|
|
// PendingMessage Pending 消息结构
|
|
|
|
|
|
type PendingMessage struct {
|
|
|
|
|
|
ID string // 消息ID
|
|
|
|
|
|
Consumer string // 消费者名称
|
|
|
|
|
|
Idle int64 // 空闲时间(毫秒)
|
|
|
|
|
|
RetryCount int64 // 重试次数
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetPendingMessages 获取待处理消息
|
|
|
|
|
|
// 使用 gredis Do() 方法执行 XPENDING 命令
|
|
|
|
|
|
func GetPendingMessages(ctx context.Context, streamKey, groupName string, start, end string, count int64) ([]PendingMessage, error) {
|
|
|
|
|
|
// XPENDING streamKey groupName start end count
|
2026-01-28 15:27:32 +08:00
|
|
|
|
result, err := getClient().Do(ctx, "XPENDING", streamKey, groupName, start, end, count)
|
2025-12-05 11:44:07 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
2025-12-05 12:18:04 +08:00
|
|
|
|
|
|
|
|
|
|
if result == nil {
|
2025-12-06 18:04:29 +08:00
|
|
|
|
return nil, nil
|
2025-12-05 12:18:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 解析返回值:[[ID, consumer, idle, retryCount], ...]
|
|
|
|
|
|
pendingArray, ok := result.Val().([]interface{})
|
|
|
|
|
|
if !ok {
|
2025-12-06 18:04:29 +08:00
|
|
|
|
return nil, nil
|
2025-12-05 12:18:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-06 18:04:29 +08:00
|
|
|
|
messages := make([]PendingMessage, 0, len(pendingArray))
|
2025-12-05 12:18:04 +08:00
|
|
|
|
for _, item := range pendingArray {
|
|
|
|
|
|
itemArray, ok := item.([]interface{})
|
|
|
|
|
|
if !ok || len(itemArray) < 4 {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
messages = append(messages, PendingMessage{
|
|
|
|
|
|
ID: gconv.String(itemArray[0]),
|
|
|
|
|
|
Consumer: gconv.String(itemArray[1]),
|
|
|
|
|
|
Idle: gconv.Int64(itemArray[2]),
|
|
|
|
|
|
RetryCount: gconv.Int64(itemArray[3]),
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return messages, nil
|
2025-12-05 11:44:07 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ClaimPendingMessage 认领超时的 Pending 消息
|
2025-12-05 12:18:04 +08:00
|
|
|
|
// 使用 gredis Do() 方法执行 XCLAIM 命令
|
2025-12-05 11:44:07 +08:00
|
|
|
|
func ClaimPendingMessage(ctx context.Context, streamKey, groupName, consumerName string, minIdleTime int64, messageIDs ...string) ([]StreamMessage, error) {
|
2025-12-05 12:18:04 +08:00
|
|
|
|
// XCLAIM streamKey groupName consumerName minIdleTime messageID1 messageID2 ...
|
|
|
|
|
|
args := []interface{}{streamKey, groupName, consumerName, minIdleTime}
|
|
|
|
|
|
for _, id := range messageIDs {
|
|
|
|
|
|
args = append(args, id)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-28 15:27:32 +08:00
|
|
|
|
result, err := getClient().Do(ctx, "XCLAIM", args...)
|
2025-12-05 11:44:07 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-05 12:18:04 +08:00
|
|
|
|
if result == nil {
|
2025-12-06 18:04:29 +08:00
|
|
|
|
return nil, nil
|
2025-12-05 12:18:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 解析返回值:类似 XREADGROUP
|
|
|
|
|
|
messagesArray, ok := result.Val().([]interface{})
|
|
|
|
|
|
if !ok {
|
2025-12-06 18:04:29 +08:00
|
|
|
|
return nil, nil
|
2025-12-05 12:18:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-06 10:13:38 +08:00
|
|
|
|
// 预分配容量,避免动态扩容
|
|
|
|
|
|
messages := make([]StreamMessage, 0, len(messagesArray))
|
2025-12-05 12:18:04 +08:00
|
|
|
|
for _, msgData := range messagesArray {
|
|
|
|
|
|
msgArray, ok := msgData.([]interface{})
|
|
|
|
|
|
if !ok || len(msgArray) < 2 {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
msgID := gconv.String(msgArray[0])
|
|
|
|
|
|
fieldsArray, ok := msgArray[1].([]interface{})
|
|
|
|
|
|
if !ok {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-06 10:13:38 +08:00
|
|
|
|
// 预分配 map 容量 ,避免动态扩容
|
|
|
|
|
|
values := make(map[string]interface{}, len(fieldsArray)/2)
|
2025-12-05 12:18:04 +08:00
|
|
|
|
for i := 0; i < len(fieldsArray); i += 2 {
|
|
|
|
|
|
if i+1 < len(fieldsArray) {
|
|
|
|
|
|
key := gconv.String(fieldsArray[i])
|
2025-12-06 10:13:38 +08:00
|
|
|
|
values[key] = fieldsArray[i+1]
|
2025-12-05 12:18:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-05 11:44:07 +08:00
|
|
|
|
messages = append(messages, StreamMessage{
|
2025-12-05 12:18:04 +08:00
|
|
|
|
ID: msgID,
|
|
|
|
|
|
Values: values,
|
2025-12-05 11:44:07 +08:00
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return messages, nil
|
2025-12-04 17:39:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// SetSessionLastActive 设置用户最后活跃时间
|
2025-12-05 12:18:04 +08:00
|
|
|
|
// 使用 gredis SetEX 方法
|
2025-12-04 17:39:31 +08:00
|
|
|
|
func SetSessionLastActive(ctx context.Context, userId string) error {
|
|
|
|
|
|
key := SessionLastActiveKeyPrefix + userId + ":last_active"
|
2025-12-06 09:36:10 +08:00
|
|
|
|
timestamp := gtime.Now().Timestamp()
|
2025-12-04 17:39:31 +08:00
|
|
|
|
|
2025-12-05 12:18:04 +08:00
|
|
|
|
// SETEX key 7200 value (7200秒 = 2小时)
|
2026-01-28 15:27:32 +08:00
|
|
|
|
_, err := getClient().Do(ctx, "SETEX", key, 7200, timestamp)
|
2025-12-05 12:18:04 +08:00
|
|
|
|
return err
|
2025-12-04 17:39:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetSessionLastActive 获取用户最后活跃时间
|
2025-12-05 12:18:04 +08:00
|
|
|
|
// 使用 gredis Get 方法
|
2025-12-04 17:39:31 +08:00
|
|
|
|
func GetSessionLastActive(ctx context.Context, userId string) (int64, error) {
|
|
|
|
|
|
key := SessionLastActiveKeyPrefix + userId + ":last_active"
|
2026-01-28 15:27:32 +08:00
|
|
|
|
result, err := getClient().Get(ctx, key)
|
2025-12-04 17:39:31 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return 0, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-05 12:18:04 +08:00
|
|
|
|
if result.IsEmpty() {
|
|
|
|
|
|
return 0, nil
|
2025-12-04 17:39:31 +08:00
|
|
|
|
}
|
2025-12-05 12:18:04 +08:00
|
|
|
|
|
|
|
|
|
|
timestamp := gconv.Int64(result.Val())
|
2025-12-05 11:44:07 +08:00
|
|
|
|
return timestamp, nil
|
2025-12-04 17:39:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// IsUserActive 检查用户是否在指定时间范围内活跃过
|
|
|
|
|
|
// 用于追问逻辑:如果用户最近活跃过,则不发送追问消息
|
|
|
|
|
|
// 参数:
|
|
|
|
|
|
// - userId: 用户ID
|
|
|
|
|
|
// - seconds: 时间范围(秒),例如传入300表示检查5分钟内是否活跃
|
|
|
|
|
|
//
|
|
|
|
|
|
// 返回:
|
|
|
|
|
|
// - bool: true表示用户在指定时间内活跃过
|
|
|
|
|
|
// - error: 操作失败时返回错误
|
|
|
|
|
|
func IsUserActive(ctx context.Context, userId string, seconds int64) (bool, error) {
|
|
|
|
|
|
lastActive, err := GetSessionLastActive(ctx, userId)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return false, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if lastActive == 0 {
|
|
|
|
|
|
return false, nil // 未找到记录,视为不活跃
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-20 14:50:41 +08:00
|
|
|
|
// 检查时间差
|
2025-12-06 09:36:10 +08:00
|
|
|
|
now := gtime.Now().Timestamp()
|
2025-12-04 17:39:31 +08:00
|
|
|
|
return (now - lastActive) < seconds, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-20 14:50:41 +08:00
|
|
|
|
// ============== 限流相关 ==============
|
2025-12-04 17:39:31 +08:00
|
|
|
|
|
2025-12-06 18:04:29 +08:00
|
|
|
|
// IncrRateLimit 增加限流计数器,返回当前计数
|
2025-12-29 14:44:32 +08:00
|
|
|
|
// key: 限流key(需要包含完整路径,如 "ip:192.168.1.1")
|
2025-12-06 18:04:29 +08:00
|
|
|
|
// windowSeconds: 时间窗口(秒)
|
|
|
|
|
|
func IncrRateLimit(ctx context.Context, key string, windowSeconds int64) (count int64, err error) {
|
2026-01-04 14:53:53 +08:00
|
|
|
|
fullKey := RateLimitKeyPrefix + key
|
2026-01-28 15:27:32 +08:00
|
|
|
|
result, err := getClient().Do(ctx, "INCR", fullKey)
|
2025-12-06 18:04:29 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
count = result.Int64()
|
|
|
|
|
|
|
|
|
|
|
|
// 首次设置过期时间
|
|
|
|
|
|
if count == 1 {
|
2026-01-28 15:27:32 +08:00
|
|
|
|
getClient().Do(ctx, "EXPIRE", fullKey, windowSeconds)
|
2025-12-06 18:04:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetRateLimit 获取当前限流计数
|
|
|
|
|
|
func GetRateLimit(ctx context.Context, key string) (count int64, err error) {
|
2026-01-04 14:53:53 +08:00
|
|
|
|
fullKey := RateLimitKeyPrefix + key
|
2026-01-28 15:27:32 +08:00
|
|
|
|
result, err := getClient().Get(ctx, fullKey)
|
2025-12-06 18:04:29 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
if result.IsEmpty() {
|
|
|
|
|
|
return 0, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
count = result.Int64()
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-20 15:04:13 +08:00
|
|
|
|
// SetSessionCache 缓存 RAGFlow Session ID(租户+用户隔离)
|
|
|
|
|
|
func SetSessionCache(ctx context.Context, tenantId, userId, sessionId string) error {
|
|
|
|
|
|
key := SessionLastActiveKeyPrefix + tenantId + ":" + userId + ":session_id"
|
2025-12-20 14:50:41 +08:00
|
|
|
|
// SETEX key 7200 value (7200秒 = 2小时,与last_active保持一致)
|
2026-01-28 15:27:32 +08:00
|
|
|
|
_, err := getClient().Do(ctx, "SETEX", key, 7200, sessionId)
|
2025-12-20 14:50:41 +08:00
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-20 15:04:13 +08:00
|
|
|
|
// GetSessionCache 获取缓存的 RAGFlow Session ID(租户+用户隔离)
|
|
|
|
|
|
func GetSessionCache(ctx context.Context, tenantId, userId string) (string, error) {
|
|
|
|
|
|
key := SessionLastActiveKeyPrefix + tenantId + ":" + userId + ":session_id"
|
2026-01-28 15:27:32 +08:00
|
|
|
|
result, err := getClient().Get(ctx, key)
|
2025-12-04 17:39:31 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return "", err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-05 12:18:04 +08:00
|
|
|
|
if result.IsEmpty() {
|
|
|
|
|
|
return "", nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return result.String(), nil
|
2025-12-04 17:39:31 +08:00
|
|
|
|
}
|
2025-12-09 09:20:44 +08:00
|
|
|
|
|
2025-12-20 15:04:13 +08:00
|
|
|
|
// DelSessionCache 删除缓存的 RAGFlow Session ID(归档时调用,租户+用户隔离)
|
|
|
|
|
|
func DelSessionCache(ctx context.Context, tenantId, userId string) error {
|
|
|
|
|
|
key := SessionLastActiveKeyPrefix + tenantId + ":" + userId + ":session_id"
|
2026-01-28 15:27:32 +08:00
|
|
|
|
_, err := getClient().Del(ctx, key)
|
2025-12-09 09:20:44 +08:00
|
|
|
|
return err
|
|
|
|
|
|
}
|
2025-12-10 18:02:31 +08:00
|
|
|
|
|
|
|
|
|
|
// TryLock 尝试获取分布式锁(非阻塞)
|
|
|
|
|
|
// key: 锁的键名
|
|
|
|
|
|
// expireSeconds: 锁的过期时间(秒),防止死锁
|
|
|
|
|
|
// 返回 true 表示获取成功,false 表示锁已被其他节点持有
|
|
|
|
|
|
func TryLock(ctx context.Context, key string, expireSeconds int) bool {
|
|
|
|
|
|
// SET key value NX EX expireSeconds
|
2026-01-28 15:27:32 +08:00
|
|
|
|
result, err := getClient().Do(ctx, "SET", key, gtime.Now().String(), "NX", "EX", expireSeconds)
|
2025-12-10 18:02:31 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
glog.Errorf(ctx, "获取分布式锁失败: %v", err)
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
return result.String() == "OK"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Unlock 释放分布式锁
|
|
|
|
|
|
func Unlock(ctx context.Context, key string) {
|
2026-01-28 15:27:32 +08:00
|
|
|
|
if _, err := getClient().Del(ctx, key); err != nil {
|
2025-12-10 18:02:31 +08:00
|
|
|
|
glog.Errorf(ctx, "释放分布式锁失败: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-16 11:52:46 +08:00
|
|
|
|
|
|
|
|
|
|
// ============== 对话计数相关(用于卡片触发)==============
|
|
|
|
|
|
|
|
|
|
|
|
const (
|
2025-12-16 15:20:16 +08:00
|
|
|
|
// UserStateKeyPrefix 用户会话状态 Key 前缀(融合阶段+计数)
|
|
|
|
|
|
UserStateKeyPrefix = "ragflow:user:state:"
|
|
|
|
|
|
// UserStateExpireSeconds 用户状态过期时间(5分钟)
|
|
|
|
|
|
UserStateExpireSeconds = 300
|
2025-12-16 11:52:46 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
2025-12-18 18:01:21 +08:00
|
|
|
|
// UserState 用户会话状态(阶段+对话计数+咨询方向,统一5分钟过期)
|
2025-12-16 15:20:16 +08:00
|
|
|
|
type UserState struct {
|
2025-12-26 15:13:07 +08:00
|
|
|
|
Stage int `json:"stage"` // 当前阶段
|
|
|
|
|
|
Direction string `json:"direction"` // 咨询方向
|
|
|
|
|
|
Count int64 `json:"count"` // 对话计数(v5.2卡片触发)
|
|
|
|
|
|
AccountName string `json:"accountName"` // 用户选择的方向对应的客服账号名称
|
2025-12-16 15:20:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetUserState 获取用户状态(阶段+计数)
|
|
|
|
|
|
func GetUserState(ctx context.Context, userId, platform string) (state *UserState, err error) {
|
|
|
|
|
|
key := UserStateKeyPrefix + userId + "_" + platform
|
2026-01-28 15:27:32 +08:00
|
|
|
|
result, err := getClient().Do(ctx, "HGETALL", key)
|
2025-12-16 11:52:46 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-18 18:01:21 +08:00
|
|
|
|
state = &UserState{Stage: 5} // 默认状态5(未选择方向)
|
2025-12-16 15:20:16 +08:00
|
|
|
|
if result.IsEmpty() {
|
2025-12-19 15:02:05 +08:00
|
|
|
|
// Redis为空,初始化默认状态
|
|
|
|
|
|
if initErr := SetUserStage(ctx, userId, platform, 5); initErr != nil {
|
|
|
|
|
|
err = initErr
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2025-12-18 18:01:21 +08:00
|
|
|
|
return
|
2025-12-16 11:52:46 +08:00
|
|
|
|
}
|
2025-12-16 15:20:16 +08:00
|
|
|
|
|
|
|
|
|
|
m := result.Map()
|
|
|
|
|
|
state.Stage = gconv.Int(m["stage"])
|
|
|
|
|
|
state.Count = gconv.Int64(m["count"])
|
2025-12-18 18:01:21 +08:00
|
|
|
|
state.Direction = gconv.String(m["direction"])
|
2025-12-16 11:52:46 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-16 15:20:16 +08:00
|
|
|
|
// SetUserStage 设置用户阶段,并刷新过期时间
|
|
|
|
|
|
func SetUserStage(ctx context.Context, userId, platform string, stage int) error {
|
|
|
|
|
|
key := UserStateKeyPrefix + userId + "_" + platform
|
2026-01-28 15:27:32 +08:00
|
|
|
|
_, err := getClient().Do(ctx, "HSET", key, "stage", stage)
|
2025-12-16 11:52:46 +08:00
|
|
|
|
if err != nil {
|
2025-12-16 15:20:16 +08:00
|
|
|
|
return err
|
2025-12-16 11:52:46 +08:00
|
|
|
|
}
|
2026-01-28 15:27:32 +08:00
|
|
|
|
_, err = getClient().Do(ctx, "EXPIRE", key, UserStateExpireSeconds)
|
2025-12-16 15:20:16 +08:00
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-26 15:13:07 +08:00
|
|
|
|
// SetUserAccountName 设置用户对应的客服账号名称,并刷新过期时间
|
|
|
|
|
|
func SetUserAccountName(ctx context.Context, userId, platform, accountName string) error {
|
2025-12-24 18:33:11 +08:00
|
|
|
|
key := UserStateKeyPrefix + userId + "_" + platform
|
2026-01-28 15:27:32 +08:00
|
|
|
|
_, err := getClient().Do(ctx, "HSET", key, "accountName", accountName)
|
2025-12-24 18:33:11 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
2026-01-28 15:27:32 +08:00
|
|
|
|
_, err = getClient().Do(ctx, "EXPIRE", key, UserStateExpireSeconds)
|
2025-12-24 18:33:11 +08:00
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-18 18:01:21 +08:00
|
|
|
|
// SetUserDirection 设置用户选择的咨询方向,并刷新过期时间
|
|
|
|
|
|
func SetUserDirection(ctx context.Context, userId, platform, direction string) error {
|
|
|
|
|
|
key := UserStateKeyPrefix + userId + "_" + platform
|
2026-01-28 15:27:32 +08:00
|
|
|
|
_, err := getClient().Do(ctx, "HSET", key, "direction", direction)
|
2025-12-18 18:01:21 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
2026-01-28 15:27:32 +08:00
|
|
|
|
_, err = getClient().Do(ctx, "EXPIRE", key, UserStateExpireSeconds)
|
2025-12-18 18:01:21 +08:00
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-16 15:20:16 +08:00
|
|
|
|
// IncrUserCount 增加用户对话计数,返回当前轮数,并刷新过期时间
|
|
|
|
|
|
func IncrUserCount(ctx context.Context, userId, platform string) (count int64, err error) {
|
|
|
|
|
|
key := UserStateKeyPrefix + userId + "_" + platform
|
2026-01-28 15:27:32 +08:00
|
|
|
|
result, err := getClient().Do(ctx, "HINCRBY", key, "count", 1)
|
2025-12-16 15:20:16 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return
|
2025-12-16 11:52:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
count = result.Int64()
|
2026-01-28 15:27:32 +08:00
|
|
|
|
_, err = getClient().Do(ctx, "EXPIRE", key, UserStateExpireSeconds)
|
2025-12-16 11:52:46 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-16 15:20:16 +08:00
|
|
|
|
// ResetUserState 重置用户状态(归档时调用)
|
|
|
|
|
|
func ResetUserState(ctx context.Context, userId, platform string) error {
|
|
|
|
|
|
key := UserStateKeyPrefix + userId + "_" + platform
|
2026-01-28 15:27:32 +08:00
|
|
|
|
_, err := getClient().Del(ctx, key)
|
2025-12-16 11:52:46 +08:00
|
|
|
|
return err
|
|
|
|
|
|
}
|
2025-12-16 15:20:16 +08:00
|
|
|
|
|
2025-12-18 18:01:21 +08:00
|
|
|
|
// ============== 对话缓存相关(5句落库)==============
|
|
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
|
// ConversationCacheKeyPrefix 对话缓存 Key 前缀
|
|
|
|
|
|
ConversationCacheKeyPrefix = "ragflow:conversation:cache:"
|
|
|
|
|
|
// ConversationCacheExpireSeconds 对话缓存过期时间(10分钟)
|
|
|
|
|
|
ConversationCacheExpireSeconds = 600
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-12-19 17:57:46 +08:00
|
|
|
|
// CacheConversation 缓存单条对话到Redis List(按sessionId存储)
|
|
|
|
|
|
func CacheConversation(ctx context.Context, sessionId string, data []byte) error {
|
|
|
|
|
|
key := ConversationCacheKeyPrefix + sessionId
|
2026-01-28 15:27:32 +08:00
|
|
|
|
_, err := getClient().Do(ctx, "RPUSH", key, string(data))
|
2025-12-18 18:01:21 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
2026-01-28 15:27:32 +08:00
|
|
|
|
_, err = getClient().Do(ctx, "EXPIRE", key, ConversationCacheExpireSeconds)
|
2025-12-18 18:01:21 +08:00
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-19 17:57:46 +08:00
|
|
|
|
// GetCachedConversations 获取缓存的对话列表并清空(按sessionId查询)
|
|
|
|
|
|
func GetCachedConversations(ctx context.Context, sessionId string) (list []string, err error) {
|
|
|
|
|
|
key := ConversationCacheKeyPrefix + sessionId
|
2026-01-28 15:27:32 +08:00
|
|
|
|
result, err := getClient().Do(ctx, "LRANGE", key, 0, -1)
|
2025-12-18 18:01:21 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
if result.IsEmpty() {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
list = result.Strings()
|
|
|
|
|
|
// 清空缓存
|
2026-01-28 15:27:32 +08:00
|
|
|
|
getClient().Del(ctx, key)
|
2025-12-18 18:01:21 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-19 17:57:46 +08:00
|
|
|
|
// GetCachedConversationCount 获取缓存的对话数量(按sessionId查询)
|
|
|
|
|
|
func GetCachedConversationCount(ctx context.Context, sessionId string) (count int64, err error) {
|
|
|
|
|
|
key := ConversationCacheKeyPrefix + sessionId
|
2026-01-28 15:27:32 +08:00
|
|
|
|
result, err := getClient().Do(ctx, "LLEN", key)
|
2025-12-18 18:01:21 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
return result.Int64(), nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-19 17:57:46 +08:00
|
|
|
|
// ClearCachedConversations 清空对话缓存(归档时调用,按sessionId)
|
|
|
|
|
|
func ClearCachedConversations(ctx context.Context, sessionId string) error {
|
|
|
|
|
|
key := ConversationCacheKeyPrefix + sessionId
|
2026-01-28 15:27:32 +08:00
|
|
|
|
_, err := getClient().Del(ctx, key)
|
2025-12-18 18:01:21 +08:00
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-16 15:20:16 +08:00
|
|
|
|
// ========== 以下为兼容旧接口(内部调用新实现)==========
|
|
|
|
|
|
|
|
|
|
|
|
// IncrConversationCount 增加用户对话计数(兼容旧接口)
|
|
|
|
|
|
func IncrConversationCount(ctx context.Context, userId, platform string, _ int64) (count int64, err error) {
|
|
|
|
|
|
return IncrUserCount(ctx, userId, platform)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetConversationCount 获取用户当前对话轮数(兼容旧接口)
|
|
|
|
|
|
func GetConversationCount(ctx context.Context, userId, platform string) (count int64, err error) {
|
|
|
|
|
|
state, err := GetUserState(ctx, userId, platform)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
return state.Count, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ResetConversationCount 重置用户对话计数(兼容旧接口)
|
|
|
|
|
|
func ResetConversationCount(ctx context.Context, userId, platform string) error {
|
|
|
|
|
|
return ResetUserState(ctx, userId, platform)
|
|
|
|
|
|
}
|