复制项目
This commit is contained in:
234
pkg/common/storage/database/mgo/redpacket.go
Normal file
234
pkg/common/storage/database/mgo/redpacket.go
Normal file
@@ -0,0 +1,234 @@
|
||||
// Copyright © 2023 OpenIM. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mgo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/database"
|
||||
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/model"
|
||||
"github.com/openimsdk/tools/db/mongoutil"
|
||||
"github.com/openimsdk/tools/db/pagination"
|
||||
"github.com/openimsdk/tools/errs"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
// RedPacketMgo implements RedPacket using MongoDB as the storage backend.
|
||||
type RedPacketMgo struct {
|
||||
coll *mongo.Collection
|
||||
}
|
||||
|
||||
// NewRedPacketMongo creates a new instance of RedPacketMgo with the provided MongoDB database.
|
||||
func NewRedPacketMongo(db *mongo.Database) (database.RedPacket, error) {
|
||||
coll := db.Collection(database.RedPacketName)
|
||||
_, err := coll.Indexes().CreateMany(context.Background(), []mongo.IndexModel{
|
||||
{
|
||||
Keys: bson.D{{Key: "red_packet_id", Value: 1}},
|
||||
Options: options.Index().SetUnique(true),
|
||||
},
|
||||
{
|
||||
Keys: bson.D{{Key: "send_user_id", Value: 1}, {Key: "create_time", Value: -1}},
|
||||
},
|
||||
{
|
||||
Keys: bson.D{{Key: "group_id", Value: 1}, {Key: "create_time", Value: -1}},
|
||||
},
|
||||
{
|
||||
Keys: bson.D{{Key: "expire_time", Value: 1}},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &RedPacketMgo{coll: coll}, nil
|
||||
}
|
||||
|
||||
// Create creates a new red packet record.
|
||||
func (r *RedPacketMgo) Create(ctx context.Context, redPacket *model.RedPacket) error {
|
||||
if redPacket.CreateTime.IsZero() {
|
||||
redPacket.CreateTime = time.Now()
|
||||
}
|
||||
return mongoutil.InsertOne(ctx, r.coll, redPacket)
|
||||
}
|
||||
|
||||
// Take retrieves a red packet by ID. Returns an error if not found.
|
||||
func (r *RedPacketMgo) Take(ctx context.Context, redPacketID string) (*model.RedPacket, error) {
|
||||
return mongoutil.FindOne[*model.RedPacket](ctx, r.coll, bson.M{"red_packet_id": redPacketID})
|
||||
}
|
||||
|
||||
// UpdateStatus updates the status of a red packet.
|
||||
func (r *RedPacketMgo) UpdateStatus(ctx context.Context, redPacketID string, status int32) error {
|
||||
return mongoutil.UpdateOne(ctx, r.coll, bson.M{"red_packet_id": redPacketID}, bson.M{"$set": bson.M{"status": status}}, false)
|
||||
}
|
||||
|
||||
// UpdateRemain updates the remain amount and count of a red packet.
|
||||
func (r *RedPacketMgo) UpdateRemain(ctx context.Context, redPacketID string, remainAmount int64, remainCount int32) error {
|
||||
update := bson.M{
|
||||
"$set": bson.M{
|
||||
"remain_amount": remainAmount,
|
||||
"remain_count": remainCount,
|
||||
},
|
||||
}
|
||||
// If remain count is 0, update status to finished
|
||||
if remainCount == 0 {
|
||||
update["$set"].(bson.M)["status"] = model.RedPacketStatusFinished
|
||||
}
|
||||
return mongoutil.UpdateOne(ctx, r.coll, bson.M{"red_packet_id": redPacketID}, update, false)
|
||||
}
|
||||
|
||||
// DecreaseRemainAtomic 原子性地减少红包剩余数量和金额(防止并发问题)
|
||||
// 只有在 remain_count > 0 且状态为 Active 时才会更新
|
||||
func (r *RedPacketMgo) DecreaseRemainAtomic(ctx context.Context, redPacketID string, amount int64) (*model.RedPacket, error) {
|
||||
// 过滤条件:红包ID匹配、剩余数量>0、状态为Active
|
||||
filter := bson.M{
|
||||
"red_packet_id": redPacketID,
|
||||
"remain_count": bson.M{"$gt": 0},
|
||||
"status": model.RedPacketStatusActive,
|
||||
}
|
||||
|
||||
// 使用 $inc 原子性地减少剩余数量和金额
|
||||
update := bson.M{
|
||||
"$inc": bson.M{
|
||||
"remain_amount": -amount,
|
||||
"remain_count": -1,
|
||||
},
|
||||
}
|
||||
|
||||
// 使用 findOneAndUpdate 返回更新后的文档
|
||||
opts := options.FindOneAndUpdate().SetReturnDocument(options.After)
|
||||
var updatedRedPacket model.RedPacket
|
||||
err := r.coll.FindOneAndUpdate(ctx, filter, update, opts).Decode(&updatedRedPacket)
|
||||
if err != nil {
|
||||
if err == mongo.ErrNoDocuments {
|
||||
// 红包不存在、已领完或状态不正确
|
||||
return nil, errs.ErrArgs.WrapMsg("red packet not available (already finished or expired)")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 如果剩余数量为0,更新状态为已完成
|
||||
if updatedRedPacket.RemainCount == 0 {
|
||||
statusUpdate := bson.M{"$set": bson.M{"status": model.RedPacketStatusFinished}}
|
||||
_ = mongoutil.UpdateOne(ctx, r.coll, bson.M{"red_packet_id": redPacketID}, statusUpdate, false)
|
||||
updatedRedPacket.Status = model.RedPacketStatusFinished
|
||||
}
|
||||
|
||||
return &updatedRedPacket, nil
|
||||
}
|
||||
|
||||
// FindExpiredRedPackets finds red packets that have expired.
|
||||
func (r *RedPacketMgo) FindExpiredRedPackets(ctx context.Context, beforeTime time.Time) ([]*model.RedPacket, error) {
|
||||
filter := bson.M{
|
||||
"expire_time": bson.M{"$lt": beforeTime},
|
||||
"status": model.RedPacketStatusActive,
|
||||
}
|
||||
return mongoutil.Find[*model.RedPacket](ctx, r.coll, filter)
|
||||
}
|
||||
|
||||
// FindRedPacketsByUser finds red packets sent by a user with pagination.
|
||||
func (r *RedPacketMgo) FindRedPacketsByUser(ctx context.Context, userID string, pagination pagination.Pagination) (total int64, redPackets []*model.RedPacket, err error) {
|
||||
filter := bson.M{"send_user_id": userID}
|
||||
return mongoutil.FindPage[*model.RedPacket](ctx, r.coll, filter, pagination, &options.FindOptions{
|
||||
Sort: bson.D{{Key: "create_time", Value: -1}},
|
||||
})
|
||||
}
|
||||
|
||||
// FindRedPacketsByGroup finds red packets in a group with pagination.
|
||||
func (r *RedPacketMgo) FindRedPacketsByGroup(ctx context.Context, groupID string, pagination pagination.Pagination) (total int64, redPackets []*model.RedPacket, err error) {
|
||||
filter := bson.M{"group_id": groupID}
|
||||
return mongoutil.FindPage[*model.RedPacket](ctx, r.coll, filter, pagination, &options.FindOptions{
|
||||
Sort: bson.D{{Key: "create_time", Value: -1}},
|
||||
})
|
||||
}
|
||||
|
||||
// FindAllRedPackets finds all red packets with pagination.
|
||||
func (r *RedPacketMgo) FindAllRedPackets(ctx context.Context, pagination pagination.Pagination) (total int64, redPackets []*model.RedPacket, err error) {
|
||||
return mongoutil.FindPage[*model.RedPacket](ctx, r.coll, bson.M{}, pagination, &options.FindOptions{
|
||||
Sort: bson.D{{Key: "create_time", Value: -1}},
|
||||
})
|
||||
}
|
||||
|
||||
// RedPacketReceiveMgo implements RedPacketReceive using MongoDB as the storage backend.
|
||||
type RedPacketReceiveMgo struct {
|
||||
coll *mongo.Collection
|
||||
}
|
||||
|
||||
// NewRedPacketReceiveMongo creates a new instance of RedPacketReceiveMgo with the provided MongoDB database.
|
||||
func NewRedPacketReceiveMongo(db *mongo.Database) (database.RedPacketReceive, error) {
|
||||
coll := db.Collection(database.RedPacketReceiveName)
|
||||
_, err := coll.Indexes().CreateMany(context.Background(), []mongo.IndexModel{
|
||||
{
|
||||
Keys: bson.D{{Key: "receive_id", Value: 1}},
|
||||
Options: options.Index().SetUnique(true),
|
||||
},
|
||||
{
|
||||
Keys: bson.D{{Key: "red_packet_id", Value: 1}, {Key: "receive_time", Value: -1}},
|
||||
},
|
||||
{
|
||||
Keys: bson.D{{Key: "receive_user_id", Value: 1}, {Key: "red_packet_id", Value: 1}},
|
||||
Options: options.Index().SetUnique(true),
|
||||
},
|
||||
{
|
||||
Keys: bson.D{{Key: "receive_user_id", Value: 1}, {Key: "receive_time", Value: -1}},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &RedPacketReceiveMgo{coll: coll}, nil
|
||||
}
|
||||
|
||||
// Create creates a new red packet receive record.
|
||||
func (r *RedPacketReceiveMgo) Create(ctx context.Context, receive *model.RedPacketReceive) error {
|
||||
if receive.ReceiveTime.IsZero() {
|
||||
receive.ReceiveTime = time.Now()
|
||||
}
|
||||
return mongoutil.InsertOne(ctx, r.coll, receive)
|
||||
}
|
||||
|
||||
// Take retrieves a receive record by ID. Returns an error if not found.
|
||||
func (r *RedPacketReceiveMgo) Take(ctx context.Context, receiveID string) (*model.RedPacketReceive, error) {
|
||||
return mongoutil.FindOne[*model.RedPacketReceive](ctx, r.coll, bson.M{"receive_id": receiveID})
|
||||
}
|
||||
|
||||
// FindByRedPacketID finds all receive records for a red packet.
|
||||
func (r *RedPacketReceiveMgo) FindByRedPacketID(ctx context.Context, redPacketID string) ([]*model.RedPacketReceive, error) {
|
||||
return mongoutil.Find[*model.RedPacketReceive](ctx, r.coll, bson.M{"red_packet_id": redPacketID}, &options.FindOptions{
|
||||
Sort: bson.D{{Key: "receive_time", Value: 1}},
|
||||
})
|
||||
}
|
||||
|
||||
// FindByUserAndRedPacketID finds if a user has received a specific red packet.
|
||||
func (r *RedPacketReceiveMgo) FindByUserAndRedPacketID(ctx context.Context, userID, redPacketID string) (*model.RedPacketReceive, error) {
|
||||
return mongoutil.FindOne[*model.RedPacketReceive](ctx, r.coll, bson.M{
|
||||
"receive_user_id": userID,
|
||||
"red_packet_id": redPacketID,
|
||||
})
|
||||
}
|
||||
|
||||
// FindByUser finds all red packets received by a user with pagination.
|
||||
func (r *RedPacketReceiveMgo) FindByUser(ctx context.Context, userID string, pagination pagination.Pagination) (total int64, receives []*model.RedPacketReceive, err error) {
|
||||
filter := bson.M{"receive_user_id": userID}
|
||||
return mongoutil.FindPage[*model.RedPacketReceive](ctx, r.coll, filter, pagination, &options.FindOptions{
|
||||
Sort: bson.D{{Key: "receive_time", Value: -1}},
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteByReceiveID deletes a receive record by receive ID (for cleanup on failure).
|
||||
func (r *RedPacketReceiveMgo) DeleteByReceiveID(ctx context.Context, receiveID string) error {
|
||||
return mongoutil.DeleteOne(ctx, r.coll, bson.M{"receive_id": receiveID})
|
||||
}
|
||||
Reference in New Issue
Block a user