redis.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  1. // Redis模块 - 负责Redis连接管理和消息存储
  2. package wsClient
  3. import (
  4. "context"
  5. "encoding/json"
  6. "fmt"
  7. "log"
  8. "strconv"
  9. "time"
  10. "github.com/redis/go-redis/v9"
  11. )
  12. // Redis客户端实例,全局变量
  13. var redisClient *redis.Client
  14. // Redis上下文
  15. var ctx = context.Background()
  16. // 分数计算常量
  17. const (
  18. UnreadMultiplier = 10000.0 // 未读消息数乘数,用于小数部分存储(增大乘数以提高精度)
  19. ContactTTL = 7 * 24 * time.Hour // 最近联系人过期时间(7天)
  20. )
  21. // ContactInfo 表示联系人信息
  22. type ContactInfo struct {
  23. ContactID int32 `json:"contact_id"` // 联系人ID
  24. Timestamp time.Time `json:"timestamp"` // 最后聊天时间
  25. UnreadCount int `json:"unread_count"` // 未读消息数
  26. ContactType string `json:"contact_type"` // 联系人类型(shop/customer)
  27. }
  28. // ChatMessage 表示存储在Redis中的聊天消息结构
  29. type ChatMessage struct {
  30. Content string `json:"content"` // 原始消息内容(JSON字符串)
  31. Timestamp time.Time `json:"timestamp"` // 存储时间戳
  32. RoomName string `json:"room_name"` // 房间名称
  33. }
  34. // InitRedis 初始化Redis连接
  35. func InitRedis(addr, password string, db int) error {
  36. redisClient = redis.NewClient(&redis.Options{
  37. Addr: addr, // Redis服务器地址 (例如: "localhost:6379")
  38. Password: password, // Redis密码 (如果没有密码则为空字符串)
  39. DB: db, // Redis数据库编号 (0-15)
  40. })
  41. // 测试Redis连接
  42. pong, err := redisClient.Ping(ctx).Result()
  43. if err != nil {
  44. log.Printf("Redis连接失败: %v", err)
  45. return err
  46. }
  47. log.Printf("Redis连接成功: %s", pong)
  48. return nil
  49. }
  50. // StoreRawMessage 将原始消息内容存储到Redis中
  51. // 使用房间名作为Redis列表的键,存储消息历史记录
  52. func StoreRawMessage(roomName string, rawMessage []byte) error {
  53. if redisClient == nil {
  54. log.Println("Redis客户端未初始化")
  55. return nil
  56. }
  57. // 创建消息对象,包含原始消息内容和时间戳
  58. chatMsg := ChatMessage{
  59. Content: string(rawMessage), // 直接存储原始JSON字符串
  60. Timestamp: time.Now(),
  61. RoomName: roomName,
  62. }
  63. // 将消息序列化为JSON
  64. msgBytes, err := json.Marshal(chatMsg)
  65. if err != nil {
  66. log.Printf("消息序列化失败: %v", err)
  67. return err
  68. }
  69. // 使用房间名作为Redis键,将消息添加到列表的右端
  70. // 键格式: "room_messages:{房间名}"
  71. key := "room_messages:" + roomName
  72. err = redisClient.RPush(ctx, key, msgBytes).Err()
  73. if err != nil {
  74. log.Printf("消息存储到Redis失败: %v", err)
  75. return err
  76. }
  77. // 设置过期时间(可选)- 7天后自动删除
  78. redisClient.Expire(ctx, key, 7*24*time.Hour)
  79. log.Printf("消息已存储到Redis - 房间: %s\n\n", roomName)
  80. return nil
  81. }
  82. // StoreMessage 将消息存储到Redis中(保留旧接口以兼容)
  83. // 使用房间名作为Redis列表的键,存储消息历史记录
  84. func StoreMessage(roomName, username, message string) error {
  85. if redisClient == nil {
  86. log.Println("Redis客户端未初始化")
  87. return nil
  88. }
  89. // 构建与新格式兼容的消息结构
  90. msgContent := map[string]interface{}{
  91. "name": username,
  92. "message": message,
  93. }
  94. msgBytes, _ := json.Marshal(msgContent)
  95. return StoreRawMessage(roomName, msgBytes)
  96. }
  97. // UpdateRecentContacts 更新最近联系人列表
  98. // 使用浮点数分数同时存储时间戳和未读消息数
  99. func UpdateRecentContacts(client *Client) error {
  100. if redisClient == nil {
  101. log.Println("Redis客户端未初始化")
  102. return nil
  103. }
  104. currentTime := time.Now().Unix()
  105. if client.userInfo.Type == "shop" {
  106. // 更新门店的联系人列表(客户)
  107. key := getContactKey("shop", client.userInfo.ShopId) // shop_chats:{ShopId}
  108. if key != "" {
  109. // 先获取当前未读消息数
  110. // currentScore, err := redisClient.ZScore(ctx, key, strconv.Itoa(int(client.userInfo.CustomId))).Result()
  111. unreadCount := 0 // ---->>>>> 门店是主动方,所以未读消息数为0
  112. // if err == nil {
  113. // // 如果成员已存在,保留当前的未读消息数
  114. // _, unreadCount = parseScore(currentScore)
  115. // }
  116. score := calculateScore(currentTime, unreadCount)
  117. redisClient.ZAdd(ctx, key, redis.Z{
  118. Score: score,
  119. Member: strconv.Itoa(int(client.userInfo.CustomId)),
  120. })
  121. // 设置过期时间
  122. redisClient.Expire(ctx, key, ContactTTL)
  123. }
  124. // 更新客户的联系人列表(门店)
  125. key = getContactKey("customer", client.userInfo.UserId) // customer_chats:{UserId}
  126. if key != "" {
  127. // 先获取当前未读消息数
  128. currentScore, err := redisClient.ZScore(ctx, key, strconv.Itoa(int(client.userInfo.ShopId))).Result()
  129. unreadCount := 0
  130. if err == nil {
  131. // 如果成员已存在,保留当前的未读消息数
  132. _, unreadCount = parseScore(currentScore)
  133. }
  134. score := calculateScore(currentTime, unreadCount)
  135. redisClient.ZAdd(ctx, key, redis.Z{
  136. Score: score,
  137. Member: strconv.Itoa(int(client.userInfo.ShopId)),
  138. })
  139. // 设置过期时间
  140. redisClient.Expire(ctx, key, ContactTTL)
  141. }
  142. }
  143. if client.userInfo.Type == "customer" {
  144. // 更新客户的联系人列表(门店)
  145. key := getContactKey("customer", client.userInfo.UserId) // customer_chats:{UserId}
  146. if key != "" {
  147. // 先获取当前未读消息数
  148. // currentScore, err := redisClient.ZScore(ctx, key, strconv.Itoa(int(client.userInfo.ShopId))).Result()
  149. unreadCount := 0 // ---->>>>> 客户是主动方,所以未读消息数为0
  150. // if err == nil {
  151. // // 如果成员已存在,保留当前的未读消息数
  152. // _, unreadCount = parseScore(currentScore)
  153. // }
  154. score := calculateScore(currentTime, unreadCount)
  155. redisClient.ZAdd(ctx, key, redis.Z{
  156. Score: score,
  157. Member: strconv.Itoa(int(client.userInfo.ShopId)),
  158. })
  159. // 设置过期时间
  160. redisClient.Expire(ctx, key, ContactTTL)
  161. }
  162. // 更新门店的联系人列表(客户)
  163. key = getContactKey("shop", client.userInfo.ShopId) // shop_chats:{ShopId}
  164. if key != "" {
  165. // 先获取当前未读消息数
  166. currentScore, err := redisClient.ZScore(ctx, key, strconv.Itoa(int(client.userInfo.CustomId))).Result()
  167. unreadCount := 0
  168. if err == nil {
  169. // 如果成员已存在,保留当前的未读消息数
  170. _, unreadCount = parseScore(currentScore)
  171. }
  172. score := calculateScore(currentTime, unreadCount)
  173. redisClient.ZAdd(ctx, key, redis.Z{
  174. Score: score,
  175. Member: strconv.Itoa(int(client.userInfo.CustomId)),
  176. })
  177. // 设置过期时间
  178. redisClient.Expire(ctx, key, ContactTTL)
  179. }
  180. }
  181. log.Printf("最近联系人列表已更新 - client name: %s", client.name)
  182. return nil
  183. }
  184. // 增加未读消息数
  185. // 为接收者(对方)的联系人列表增加未读消息数
  186. func IncrementUnreadCount(sender *Client, count int) error {
  187. if redisClient == nil {
  188. log.Println("Redis客户端未初始化")
  189. return nil
  190. }
  191. // 根据发送者的类型确定接收者的类型和ID
  192. var receiverType string
  193. var receiverID int32
  194. var senderMember string
  195. if sender.userInfo.Type == "shop" {
  196. // 门店发送消息给客户,接收者是客户
  197. receiverType = "customer"
  198. receiverID = sender.userInfo.UserId
  199. senderMember = strconv.Itoa(int(sender.userInfo.ShopId))
  200. } else if sender.userInfo.Type == "customer" {
  201. // 客户发送消息给门店,接收者是门店
  202. receiverType = "shop"
  203. receiverID = sender.userInfo.ShopId
  204. senderMember = strconv.Itoa(int(sender.userInfo.CustomId))
  205. } else {
  206. log.Printf("未知的客户端类型: %s", sender.userInfo.Type)
  207. return nil
  208. }
  209. // 获取接收者的联系人列表键
  210. key := getContactKey(receiverType, receiverID)
  211. if key == "" {
  212. log.Printf("无法生成联系人键 - 类型: %s, ID: %d", receiverType, receiverID)
  213. return nil
  214. }
  215. // 获取当前分数
  216. currentScore, err := redisClient.ZScore(ctx, key, senderMember).Result()
  217. if err != nil {
  218. // 如果成员不存在,使用当前时间戳和0未读消息数创建
  219. currentScore = calculateScore(time.Now().Unix(), 0)
  220. }
  221. // 解析当前的分数
  222. _, currentUnread := parseScore(currentScore)
  223. // 增加未读消息数
  224. newUnread := currentUnread + count
  225. // 重新计算分数
  226. timestamp := time.Now().Unix()
  227. newScore := calculateScore(timestamp, newUnread)
  228. // 更新分数
  229. redisClient.ZAdd(ctx, key, redis.Z{
  230. Score: newScore,
  231. Member: senderMember,
  232. })
  233. // 设置过期时间
  234. redisClient.Expire(ctx, key, ContactTTL)
  235. log.Printf("未读消息数已增加 - 接收者类型: %s, 接收者ID: %d, 发送者ID: %s, 未读数: %d, 新分数: %f",
  236. receiverType, receiverID, senderMember, newUnread, newScore)
  237. return nil
  238. }
  239. // GetRoomMessages 获取房间的历史消息
  240. // 返回指定数量的最新消息
  241. func GetRoomMessages(roomName string, count int64) ([]ChatMessage, error) {
  242. if redisClient == nil {
  243. log.Println("Redis客户端未初始化")
  244. return nil, nil
  245. }
  246. key := "room_messages:" + roomName
  247. // 获取列表中的最新消息(从右端开始,即最新的消息)
  248. // -count 表示从列表末尾开始的count个元素
  249. result, err := redisClient.LRange(ctx, key, -count, -1).Result()
  250. if err != nil {
  251. log.Printf("从Redis获取消息失败: %v", err)
  252. return nil, err
  253. }
  254. var messages []ChatMessage
  255. for _, msgStr := range result {
  256. var msg ChatMessage
  257. if err := json.Unmarshal([]byte(msgStr), &msg); err != nil {
  258. log.Printf("消息反序列化失败: %v", err)
  259. continue
  260. }
  261. messages = append(messages, msg)
  262. }
  263. return messages, nil
  264. }
  265. // GetRoomMessageCount 获取房间的消息总数
  266. func GetRoomMessageCount(roomName string) (int64, error) {
  267. if redisClient == nil {
  268. return 0, nil
  269. }
  270. key := "room_messages:" + roomName
  271. return redisClient.LLen(ctx, key).Result()
  272. }
  273. // CloseRedis 关闭Redis连接
  274. func CloseRedis() error {
  275. if redisClient != nil {
  276. return redisClient.Close()
  277. }
  278. return nil
  279. }
  280. // calculateScore 计算有序集合的分数,同时存储时间戳和未读消息数
  281. // 整数部分:Unix时间戳,小数部分:未读消息数(除以乘数)
  282. func calculateScore(timestamp int64, unreadCount int) float64 {
  283. return float64(timestamp) + (float64(unreadCount) / UnreadMultiplier)
  284. }
  285. // parseScore 从分数中解析出时间戳和未读消息数
  286. func parseScore(score float64) (timestamp int64, unreadCount int) {
  287. timestamp = int64(score) // 整数部分是时间戳
  288. decimalPart := score - float64(timestamp)
  289. // 使用四舍五入来处理浮点数精度问题
  290. unreadCount = int(decimalPart*UnreadMultiplier + 0.5)
  291. return
  292. }
  293. // getContactKey 生成联系人的Redis键
  294. func getContactKey(userType string, userID int32) string {
  295. if userType == "shop" {
  296. return "shop_chats:" + strconv.Itoa(int(userID))
  297. } else if userType == "customer" {
  298. return "customer_chats:" + strconv.Itoa(int(userID))
  299. }
  300. return ""
  301. }
  302. // GetRecentContacts 获取用户的最近联系人列表
  303. // 返回按最后聊天时间排序的联系人列表(最新的在前)
  304. func GetRecentContacts(userType string, userID int32, limit int64) ([]ContactInfo, error) {
  305. if redisClient == nil {
  306. log.Println("Redis客户端未初始化")
  307. return nil, nil
  308. }
  309. key := getContactKey(userType, userID)
  310. if key == "" {
  311. return nil, fmt.Errorf("无效的用户类型: %s", userType)
  312. }
  313. // 获取最近的联系人(按分数降序,最新的在前)
  314. result, err := redisClient.ZRevRangeWithScores(ctx, key, 0, limit-1).Result()
  315. if err != nil {
  316. log.Printf("获取最近联系人失败: %v", err)
  317. return nil, err
  318. }
  319. var contacts []ContactInfo
  320. for _, item := range result {
  321. contactID, err := strconv.Atoi(item.Member.(string))
  322. if err != nil {
  323. log.Printf("解析联系人ID失败: %v", err)
  324. continue
  325. }
  326. timestamp, unreadCount := parseScore(item.Score)
  327. // 确定联系人的类型
  328. contactType := "customer"
  329. if userType == "customer" {
  330. contactType = "shop"
  331. }
  332. contact := ContactInfo{
  333. ContactID: int32(contactID),
  334. Timestamp: time.Unix(timestamp, 0),
  335. UnreadCount: unreadCount,
  336. ContactType: contactType,
  337. }
  338. contacts = append(contacts, contact)
  339. }
  340. return contacts, nil
  341. }
  342. // GetTotalUnreadCount 获取用户的总未读消息数
  343. func GetTotalUnreadCount(userType string, userID int32) (int, error) {
  344. if redisClient == nil {
  345. log.Println("Redis客户端未初始化")
  346. return 0, nil
  347. }
  348. key := getContactKey(userType, userID)
  349. if key == "" {
  350. return 0, fmt.Errorf("无效的用户类型: %s", userType)
  351. }
  352. // 获取所有联系人的分数
  353. result, err := redisClient.ZRangeWithScores(ctx, key, 0, -1).Result()
  354. if err != nil {
  355. log.Printf("获取联系人分数失败: %v", err)
  356. return 0, err
  357. }
  358. totalUnread := 0
  359. for _, item := range result {
  360. _, unreadCount := parseScore(item.Score)
  361. totalUnread += unreadCount
  362. }
  363. return totalUnread, nil
  364. }
  365. // ClearUnreadCount 清除指定联系人的未读消息数
  366. func ClearUnreadCount(userType string, userID int32, contactID int32) error {
  367. if redisClient == nil {
  368. log.Println("Redis客户端未初始化")
  369. return nil
  370. }
  371. key := getContactKey(userType, userID)
  372. if key == "" {
  373. return fmt.Errorf("无效的用户类型: %s", userType)
  374. }
  375. // 获取当前分数
  376. currentScore, err := redisClient.ZScore(ctx, key, strconv.Itoa(int(contactID))).Result()
  377. if err != nil {
  378. // 如果联系人不存在,不需要清理
  379. return nil
  380. }
  381. // 解析当前分数,保留时间戳,清除未读消息数
  382. timestamp, _ := parseScore(currentScore)
  383. newScore := calculateScore(timestamp, 0)
  384. // 更新分数
  385. redisClient.ZAdd(ctx, key, redis.Z{
  386. Score: newScore,
  387. Member: strconv.Itoa(int(contactID)),
  388. })
  389. log.Printf("未读消息数已清除 - 用户类型: %s, 用户ID: %d, 联系人ID: %d",
  390. userType, userID, contactID)
  391. return nil
  392. }