mirror of
https://github.com/openimsdk/open-im-server.git
synced 2026-06-15 20:13:45 +08:00
会话静音
This commit is contained in:
parent
1a3d7020ad
commit
8cd3dff8f8
@ -1,14 +1,14 @@
|
||||
# Cursor 代码索引忽略(语法与 .gitignore 相同)
|
||||
# 与根目录 .gitignore 对齐;未列出的规则仍以 .gitignore 为准(Git 不索引的路径 Cursor 通常也不关心)
|
||||
# 与根目录 .gitignore 对齐;以下为补充规则,减少生成物/文档噪音,保留业务源码与 .proto
|
||||
|
||||
### OpenIM(与 .gitignore 一致)###
|
||||
logs
|
||||
.devcontainer
|
||||
components
|
||||
out-test
|
||||
logs/
|
||||
.devcontainer/
|
||||
components/
|
||||
out-test/
|
||||
Dockerfile.cross
|
||||
|
||||
### macOS / 本地工具(不入索引)###
|
||||
### macOS / 本地工具 ###
|
||||
.DS_Store
|
||||
.playwright-mcp/
|
||||
|
||||
@ -17,26 +17,51 @@ tmp/
|
||||
bin/
|
||||
output/
|
||||
_output/
|
||||
build/
|
||||
dist/
|
||||
deployments/charts/generated-configs/
|
||||
|
||||
### 配置与密钥(勿入索引)###
|
||||
.env
|
||||
config/config.yaml
|
||||
config/notification.yaml
|
||||
start-config.yml
|
||||
|
||||
### 部署生成物 ###
|
||||
deployments/openim-server/charts
|
||||
deployments/openim-server/charts/
|
||||
|
||||
### 本地笔记 ###
|
||||
.idea.md
|
||||
.todo.md
|
||||
.note.md
|
||||
|
||||
### 生成代码(以 .proto 为准,勿重复索引)###
|
||||
protocol/**/*.pb.go
|
||||
protocol/**/*_grpc.pb.go
|
||||
|
||||
### 文档与资源(保留 docs/contrib、根 README;忽略多语言 readme 与静态资源)###
|
||||
docs/readme/
|
||||
docs/.generated_docs
|
||||
docs/contributing/
|
||||
assets/
|
||||
virgil_chat_server_design.md
|
||||
docs/virgil-e2ee-*.md
|
||||
|
||||
### 测试与脚本输出 ###
|
||||
test/e2e/output/
|
||||
scripts/**/*.log
|
||||
|
||||
### 通用备份与临时文件 ###
|
||||
*.bak
|
||||
*.gho
|
||||
*.ori
|
||||
*.orig
|
||||
*.tmp
|
||||
*~
|
||||
dist/
|
||||
*.BACKUP.*
|
||||
*.BASE.*
|
||||
*.LOCAL.*
|
||||
*.REMOTE.*
|
||||
|
||||
### VS Code(除团队共享配置外)###
|
||||
.vscode/*
|
||||
@ -44,6 +69,7 @@ dist/
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
*.code-workspace
|
||||
|
||||
### Go ###
|
||||
*.exe
|
||||
@ -54,13 +80,19 @@ dist/
|
||||
*.test
|
||||
*.out
|
||||
vendor/
|
||||
go.work
|
||||
go.work.sum
|
||||
go.sum
|
||||
|
||||
### JetBrains / IDE ###
|
||||
.idea/
|
||||
out/
|
||||
|
||||
### Tags ###
|
||||
### Git / CI(低价值索引)###
|
||||
.git/
|
||||
.github/
|
||||
|
||||
### Tags / 索引工具 ###
|
||||
TAGS
|
||||
tags
|
||||
gtags.files
|
||||
@ -70,3 +102,5 @@ GPATH
|
||||
GSYMS
|
||||
cscope.files
|
||||
cscope.out
|
||||
cscope.in.out
|
||||
cscope.po.out
|
||||
|
||||
@ -71,3 +71,7 @@ func (o *ConversationApi) GetNotNotifyConversationIDs(c *gin.Context) {
|
||||
func (o *ConversationApi) GetPinnedConversationIDs(c *gin.Context) {
|
||||
a2r.Call(c, conversation.ConversationClient.GetPinnedConversationIDs, o.Client)
|
||||
}
|
||||
|
||||
func (o *ConversationApi) SetMute(c *gin.Context) {
|
||||
a2r.Call(c, conversation.ConversationClient.SetConversationMute, o.Client)
|
||||
}
|
||||
|
||||
@ -131,13 +131,6 @@ func (o *FriendApi) AddOnewayFriend(c *gin.Context) {
|
||||
a2r.Call(c, relation.FriendClient.AddOnewayFriend, o.Client)
|
||||
}
|
||||
|
||||
func (o *FriendApi) SetMute(c *gin.Context) {
|
||||
a2r.Call(c, relation.FriendClient.SetMute, o.Client)
|
||||
}
|
||||
|
||||
func (o *FriendApi) GetMute(c *gin.Context) {
|
||||
a2r.Call(c, relation.FriendClient.GetMute, o.Client)
|
||||
}
|
||||
|
||||
func (o *FriendApi) PinFriend(c *gin.Context) {
|
||||
a2r.Call(c, relation.FriendClient.PinFriend, o.Client)
|
||||
|
||||
@ -222,8 +222,6 @@ func newGinRouter(ctx context.Context, client discovery.SvcDiscoveryRegistry, co
|
||||
friendRouterGroup.POST("/get_self_unhandled_apply_count", f.GetSelfUnhandledApplyCount)
|
||||
friendRouterGroup.POST("/get_pinned_friend_ids", f.GetPinnedFriendIDs)
|
||||
friendRouterGroup.POST("/add_oneway_friend", f.AddOnewayFriend)
|
||||
friendRouterGroup.POST("/set_mute", f.SetMute)
|
||||
friendRouterGroup.POST("/get_mute", f.GetMute)
|
||||
friendRouterGroup.POST("/pin", f.PinFriend)
|
||||
friendRouterGroup.POST("/unpin", f.UnpinFriend)
|
||||
}
|
||||
@ -358,6 +356,7 @@ func newGinRouter(ctx context.Context, client discovery.SvcDiscoveryRegistry, co
|
||||
conversationGroup.POST("/get_owner_conversation", c.GetOwnerConversation)
|
||||
conversationGroup.POST("/get_not_notify_conversation_ids", c.GetNotNotifyConversationIDs)
|
||||
conversationGroup.POST("/get_pinned_conversation_ids", c.GetPinnedConversationIDs)
|
||||
conversationGroup.POST("/set_mute", c.SetMute)
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@ -49,7 +49,6 @@ type conversationServer struct {
|
||||
pbconversation.UnimplementedConversationServer
|
||||
conversationDatabase controller.ConversationDatabase
|
||||
msgBurnDeadlineDB database.MsgBurnDeadline
|
||||
userMuteDB controller.UserMuteDatabase
|
||||
|
||||
conversationNotificationSender *ConversationNotificationSender
|
||||
config *Config
|
||||
@ -86,10 +85,6 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
userMuteMongoDB, err := mgo.NewUserMuteMongo(mgocli.GetDB())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
userConn, err := client.GetConn(ctx, config.Share.RpcRegisterName.User)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -109,7 +104,6 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
|
||||
conversationDatabase: controller.NewConversationDatabase(conversationDB,
|
||||
redis.NewConversationRedis(rdb, &config.LocalCacheConfig, redis.GetRocksCacheOptions(), conversationDB), mgocli.GetTx()),
|
||||
msgBurnDeadlineDB: msgBurnDeadlineDB,
|
||||
userMuteDB: controller.NewUserMuteDatabase(userMuteMongoDB),
|
||||
userClient: rpcli.NewUserClient(userConn),
|
||||
groupClient: rpcli.NewGroupClient(groupConn),
|
||||
msgClient: msgClient,
|
||||
@ -202,14 +196,14 @@ func (c *conversationServer) GetSortedConversationList(ctx context.Context, req
|
||||
}
|
||||
conversation_notPinTime[time] = conversationID
|
||||
}
|
||||
if c.userMuteDB != nil {
|
||||
for _, v := range conversations {
|
||||
elem, ok := conversationMsg[v.ConversationID]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
c.fillConversationElemUserMute(ctx, c.userMuteDB, req.UserID, elem, v.ConversationType, v.UserID)
|
||||
for _, v := range conversations {
|
||||
elem, ok := conversationMsg[v.ConversationID]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
elem.MuteDuration = v.MuteDuration
|
||||
elem.MuteEndTime = v.MuteEndTime
|
||||
elem.IsMuted = computeIsMuted(v.MuteDuration, v.MuteEndTime)
|
||||
}
|
||||
resp = &pbconversation.GetSortedConversationListResp{
|
||||
ConversationTotal: int64(len(chatLogs)),
|
||||
@ -916,3 +910,34 @@ func (c *conversationServer) ClearBurnExpiredMsgs(ctx context.Context, req *pbco
|
||||
}
|
||||
return &pbconversation.ClearBurnExpiredMsgsResp{Count: processed}, nil
|
||||
}
|
||||
|
||||
func (c *conversationServer) SetConversationMute(ctx context.Context, req *pbconversation.SetConversationMuteReq) (*pbconversation.SetConversationMuteResp, error) {
|
||||
var (
|
||||
muteDuration int32
|
||||
muteEndTime int64
|
||||
)
|
||||
switch {
|
||||
case req.Duration == 0:
|
||||
// 取消静音:清零所有静音字段
|
||||
case req.Duration == -1:
|
||||
// 永久静音
|
||||
muteDuration = -1
|
||||
default:
|
||||
// 定时静音
|
||||
muteDuration = req.Duration
|
||||
muteEndTime = time.Now().Unix() + int64(req.Duration)
|
||||
}
|
||||
if err := c.conversationDatabase.UpdateUsersConversationField(
|
||||
ctx,
|
||||
[]string{req.OwnerUserID},
|
||||
req.ConversationID,
|
||||
map[string]any{
|
||||
"mute_duration": muteDuration,
|
||||
"mute_end_time": muteEndTime,
|
||||
},
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.conversationNotificationSender.ConversationChangeNotification(ctx, req.OwnerUserID, []string{req.ConversationID})
|
||||
return &pbconversation.SetConversationMuteResp{}, nil
|
||||
}
|
||||
|
||||
@ -4,95 +4,37 @@ package conversation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/controller"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
"github.com/openimsdk/protocol/constant"
|
||||
pbconversation "github.com/openimsdk/protocol/conversation"
|
||||
"github.com/openimsdk/tools/log"
|
||||
)
|
||||
|
||||
// int64MuteDurationToProto 将 user_mute 的秒数配置写入 Conversation.muteDuration(int32);正数过大时截断。
|
||||
func int64MuteDurationToProto(d int64) int32 {
|
||||
if d > int64(math.MaxInt32) {
|
||||
return math.MaxInt32
|
||||
// computeIsMuted 根据会话模型中存储的 mute_duration 和 mute_end_time 计算当前是否处于静音状态:
|
||||
// - duration == 0 且 end == 0:未静音
|
||||
// - duration == -1 且 end == 0:永久静音
|
||||
// - end > 0 且 end > now:定时静音仍有效
|
||||
// - end > 0 且 end <= now:定时静音已过期,视为未静音
|
||||
func computeIsMuted(muteDuration int32, muteEndTime int64) bool {
|
||||
if muteDuration == 0 && muteEndTime == 0 {
|
||||
return false
|
||||
}
|
||||
if d < int64(math.MinInt32) {
|
||||
return math.MinInt32
|
||||
if muteDuration == -1 && muteEndTime == 0 {
|
||||
return true
|
||||
}
|
||||
return int32(d)
|
||||
return muteEndTime > time.Now().Unix()
|
||||
}
|
||||
|
||||
// conversationMuteFromRecord 与 relation.GetMute 判定一致:未记录/已过期则未静音;永久为 duration=-1 且 end=0。
|
||||
func conversationMuteFromRecord(rec *model.UserMute, nowUnix int64) (isMuted bool, muteDuration int32, muteEndTime int64) {
|
||||
if rec == nil {
|
||||
return false, 0, 0
|
||||
}
|
||||
if rec.MuteEndTime != 0 && rec.MuteEndTime <= nowUnix {
|
||||
return false, 0, 0
|
||||
}
|
||||
d := rec.MuteDuration
|
||||
if d == 0 && rec.MuteEndTime == 0 {
|
||||
d = -1
|
||||
}
|
||||
md := int64MuteDurationToProto(d)
|
||||
me := rec.MuteEndTime
|
||||
isMuted = (md == -1) || (me > nowUnix)
|
||||
return isMuted, md, me
|
||||
}
|
||||
|
||||
func (c *conversationServer) fillConversationUserMute(ctx context.Context, conv *pbconversation.Conversation) {
|
||||
if c == nil || c.userMuteDB == nil || conv == nil {
|
||||
// fillConversationUserMute 根据会话模型字段(已由 ConversationDB2Pb 通过 CopyStructFields 填入
|
||||
// conv.MuteDuration / conv.MuteEndTime)计算并设置 conv.IsMuted,无需额外数据库查询。
|
||||
func (c *conversationServer) fillConversationUserMute(_ context.Context, conv *pbconversation.Conversation) {
|
||||
if conv == nil {
|
||||
return
|
||||
}
|
||||
if conv.ConversationType != constant.SingleChatType || conv.UserID == "" {
|
||||
return
|
||||
}
|
||||
rec, err := c.userMuteDB.Get(ctx, conv.OwnerUserID, conv.UserID)
|
||||
if err != nil {
|
||||
log.ZWarn(ctx, "fillConversationUserMute Get", err, "owner", conv.OwnerUserID, "peer", conv.UserID)
|
||||
return
|
||||
}
|
||||
now := time.Now().Unix()
|
||||
isMuted, dur, end := conversationMuteFromRecord(rec, now)
|
||||
conv.IsMuted = isMuted
|
||||
conv.MuteDuration = dur
|
||||
conv.MuteEndTime = end
|
||||
conv.IsMuted = computeIsMuted(conv.MuteDuration, conv.MuteEndTime)
|
||||
}
|
||||
|
||||
func (c *conversationServer) fillConversationsUserMute(ctx context.Context, list []*pbconversation.Conversation) {
|
||||
if len(list) == 0 {
|
||||
return
|
||||
}
|
||||
for _, conv := range list {
|
||||
c.fillConversationUserMute(ctx, conv)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *conversationServer) fillConversationElemUserMute(
|
||||
ctx context.Context,
|
||||
db controller.UserMuteDatabase,
|
||||
ownerUserID string,
|
||||
elem *pbconversation.ConversationElem,
|
||||
conversationType int32,
|
||||
peerUserID string,
|
||||
) {
|
||||
if db == nil || elem == nil || ownerUserID == "" {
|
||||
return
|
||||
}
|
||||
if conversationType != constant.SingleChatType || peerUserID == "" {
|
||||
return
|
||||
}
|
||||
rec, err := db.Get(ctx, ownerUserID, peerUserID)
|
||||
if err != nil {
|
||||
log.ZWarn(ctx, "fillConversationElemUserMute Get", err, "owner", ownerUserID, "peer", peerUserID)
|
||||
return
|
||||
}
|
||||
now := time.Now().Unix()
|
||||
isMuted, dur, end := conversationMuteFromRecord(rec, now)
|
||||
elem.IsMuted = isMuted
|
||||
elem.MuteDuration = dur
|
||||
elem.MuteEndTime = end
|
||||
}
|
||||
|
||||
@ -72,7 +72,6 @@ type msgServer struct {
|
||||
conversationClient *rpcli.ConversationClient
|
||||
spamReportDB database.SpamReport
|
||||
globalBlackDB controller.UserGlobalBlackDatabase
|
||||
userMuteDB controller.UserMuteDatabase
|
||||
msgBurnDeadlineDB database.MsgBurnDeadline
|
||||
}
|
||||
|
||||
@ -138,10 +137,6 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
userMuteMgo, err := mgo.NewUserMuteMongo(mgocli.GetDB())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s := &msgServer{
|
||||
MsgDatabase: msgDatabase,
|
||||
RegisterCenter: client,
|
||||
@ -154,7 +149,6 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
|
||||
conversationClient: conversationClient,
|
||||
spamReportDB: spamReportDB,
|
||||
globalBlackDB: controller.NewUserGlobalBlackDatabase(globalBlackMgo),
|
||||
userMuteDB: controller.NewUserMuteDatabase(userMuteMgo),
|
||||
msgBurnDeadlineDB: msgBurnDeadlineDB,
|
||||
}
|
||||
|
||||
|
||||
@ -325,14 +325,20 @@ func (m *msgServer) modifyMessageByUserMessageReceiveOpt(ctx context.Context, us
|
||||
}
|
||||
}
|
||||
|
||||
// 第四优先级:用户静音设置(user_mute 集合,支持好友与非好友)
|
||||
// 无论会话记录是否存在均检查,以支持对非好友的静音
|
||||
if m.userMuteDB != nil {
|
||||
muted, err := m.userMuteDB.IsMuted(ctx, userID, pb.MsgData.SendID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
// 第四优先级:会话静音设置(存储于 conversations 集合的 mute_duration/mute_end_time)
|
||||
conv, convErr := m.ConversationLocalCache.GetConversation(ctx, userID, conversationID)
|
||||
if convErr != nil && !errs.ErrRecordNotFound.Is(convErr) {
|
||||
return false, convErr
|
||||
}
|
||||
if convErr == nil && conv != nil {
|
||||
var isMuted bool
|
||||
switch {
|
||||
case conv.MuteDuration == -1 && conv.MuteEndTime == 0:
|
||||
isMuted = true
|
||||
case conv.MuteEndTime > 0:
|
||||
isMuted = conv.MuteEndTime > time.Now().Unix()
|
||||
}
|
||||
if muted {
|
||||
if isMuted {
|
||||
if pb.MsgData.Options == nil {
|
||||
pb.MsgData.Options = make(map[string]bool, 10)
|
||||
}
|
||||
|
||||
@ -37,4 +37,6 @@ type Conversation struct {
|
||||
IsMsgDestruct bool `bson:"is_msg_destruct"`
|
||||
MsgDestructTime int64 `bson:"msg_destruct_time"`
|
||||
LatestMsgDestructTime time.Time `bson:"latest_msg_destruct_time"`
|
||||
MuteDuration int32 `bson:"mute_duration"`
|
||||
MuteEndTime int64 `bson:"mute_end_time"`
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user