增加任意送礼增加亲密度

This commit is contained in:
zhx 2026-06-11 18:33:30 +08:00
parent f4eee4db4c
commit f995d80610
4 changed files with 152 additions and 6 deletions

View File

@ -758,10 +758,8 @@ func roomGiftCPEventFromRoomMessage(body []byte) (cpdomain.GiftEvent, bool, erro
if err := proto.Unmarshal(envelope.GetBody(), &sent); err != nil {
return cpdomain.GiftEvent{}, false, err
}
// 只有明确标记 cp_relation_type 的礼物才进入关系逻辑;普通礼物仍由其他读模型消费。
if strings.TrimSpace(sent.GetCpRelationType()) == "" {
return cpdomain.GiftEvent{}, false, nil
}
// CP worker 必须接收所有送礼事实:没有 active 关系时仓储层只让 CP/兄弟/姐妹礼物生成申请;
// 已有 active 关系时,普通、幸运和超级幸运礼物都按 wallet 已结算的 GiftValue 增加亲密值。
return cpdomain.GiftEvent{
AppCode: appcode.Normalize(envelope.GetAppCode()),
EventID: envelope.GetEventId(),

View File

@ -0,0 +1,49 @@
package app
import (
"testing"
"google.golang.org/protobuf/proto"
roomeventsv1 "hyapp.local/api/proto/events/room/v1"
"hyapp/pkg/roommq"
)
func TestRoomGiftCPEventFromRoomMessageIncludesNormalGift(t *testing.T) {
body, err := proto.Marshal(&roomeventsv1.RoomGiftSent{
SenderUserId: 1001,
TargetUserId: 1002,
GiftId: "rose",
GiftCount: 1,
GiftValue: 100,
BillingReceiptId: "receipt-normal",
CommandId: "cmd-normal",
GiftTypeCode: "normal",
GiftName: "Rose",
})
if err != nil {
t.Fatalf("marshal room gift sent failed: %v", err)
}
encoded, err := roommq.EncodeRoomOutboxMessage(&roomeventsv1.EventEnvelope{
AppCode: "lalu",
EventId: "room_evt_normal_gift",
EventType: "RoomGiftSent",
RoomId: "room-1",
RoomVersion: 7,
OccurredAtMs: 1700000000000,
Body: body,
})
if err != nil {
t.Fatalf("encode room outbox message failed: %v", err)
}
event, ok, err := roomGiftCPEventFromRoomMessage(encoded)
if err != nil {
t.Fatalf("convert room gift event failed: %v", err)
}
if !ok {
t.Fatal("normal gifts must reach CP consumer so active relationships can gain intimacy")
}
if event.CPRelationType != "" || event.GiftTypeCode != "normal" || event.GiftValue != 100 || event.TargetUserID != 1002 {
t.Fatalf("normal gift event mismatch: %+v", event)
}
}

View File

@ -514,7 +514,7 @@ func (r *Repository) ConsumeGiftEvent(ctx context.Context, event cpdomain.GiftEv
return cpdomain.ConsumeResult{}, xerr.New(xerr.Unavailable, "mysql repository is not configured")
}
relationType := normalizeRelationType(event.CPRelationType)
if relationType == "" || event.SenderUserID <= 0 || event.TargetUserID <= 0 || event.SenderUserID == event.TargetUserID {
if event.SenderUserID <= 0 || event.TargetUserID <= 0 || event.SenderUserID == event.TargetUserID {
return cpdomain.ConsumeResult{Consumed: false}, nil
}
if strings.TrimSpace(event.EventID) == "" {
@ -540,7 +540,7 @@ func (r *Repository) ConsumeGiftEvent(ctx context.Context, event cpdomain.GiftEv
return cpdomain.ConsumeResult{Consumed: false}, tx.Commit()
}
userAID, userBID := orderedPair(event.SenderUserID, event.TargetUserID)
// 已有 active 关系时,任何关系礼物都只增加这对关系的亲密值,不再生成新的申请
// 已有 active 关系时,任意礼物都只增加这对关系的亲密值GiftValue 已由 wallet-service 按普通/幸运/超级幸运比例结算
if relationship, found, err := lockActiveRelationshipTx(ctx, tx, appCode, userAID, userBID); err != nil {
return cpdomain.ConsumeResult{}, err
} else if found {
@ -556,6 +556,10 @@ func (r *Repository) ConsumeGiftEvent(ctx context.Context, event cpdomain.GiftEv
}
return cpdomain.ConsumeResult{Consumed: true, Relationship: updated}, tx.Commit()
}
if relationType == "" {
// 普通礼物在双方还没有 active 关系时不能创建申请;这类事件不写消费位点,后续若 MQ 重投仍然是无副作用 no-op。
return cpdomain.ConsumeResult{Consumed: false}, tx.Commit()
}
// 没有 active 关系时CP/兄弟/姐妹礼物只生成 pending 申请;真正建立关系必须由 B 显式同意。
if err := ensurePairHasNoActiveRelationshipTx(ctx, tx, appCode, userAID, userBID, event.TargetUserID); err != nil {
if err := insertConsumptionTx(ctx, tx, appCode, event.EventID, "skipped", "", "", "existing_relationship", nowMs); err != nil {

View File

@ -176,6 +176,101 @@ func TestListActiveIntimacyLeaderboardEntriesOrdersAndIncludesBothUsers(t *testi
}
}
func TestConsumeGiftEventAddsIntimacyForNormalGiftWhenRelationshipExists(t *testing.T) {
schema := mysqlschema.New(t)
repo := New(schema.DB)
ctx := appcode.WithContext(context.Background(), appcode.Default)
nowMS := int64(1700000000000)
seedCPTestUser(t, schema.DB, 4201, "Normal Gift Alice", nowMS)
seedCPTestUser(t, schema.DB, 4202, "Normal Gift Bob", nowMS)
seedCPLevelRules(t, schema.DB, cpdomain.RelationTypeCP, []int64{0, 100, 300}, nowMS)
seedActiveCPRelationship(t, schema.DB, "normal_gift_cp", cpdomain.RelationTypeCP, 4201, 4202, 90, 1, nowMS)
result, err := repo.ConsumeGiftEvent(ctx, cpdomain.GiftEvent{
AppCode: appcode.Default,
EventID: "room_evt_normal_gift_cp",
RoomID: "room-normal",
RoomVersion: 3,
OccurredAtMS: nowMS + 1,
SenderUserID: 4201,
TargetUserID: 4202,
GiftID: "rose",
GiftCount: 1,
GiftValue: 37,
BillingReceiptID: "receipt-normal-gift-cp",
CommandID: "cmd-normal-gift-cp",
GiftTypeCode: "normal",
}, nowMS+2)
if err != nil {
t.Fatalf("consume normal gift for active cp failed: %v", err)
}
if !result.Consumed || result.Relationship.RelationshipID != "normal_gift_cp" {
t.Fatalf("normal gift must update existing relationship: %+v", result)
}
if result.Relationship.IntimacyValue != 127 || result.Relationship.Level != 2 {
t.Fatalf("relationship intimacy/level mismatch: %+v", result.Relationship)
}
var status string
if err := schema.DB.QueryRowContext(ctx, `
SELECT status
FROM user_cp_gift_event_consumption
WHERE app_code = ? AND room_event_id = ?`,
appcode.Default, "room_evt_normal_gift_cp",
).Scan(&status); err != nil {
t.Fatalf("read gift consumption failed: %v", err)
}
if status != "relationship_intimacy_added" {
t.Fatalf("normal gift consumption status mismatch: %s", status)
}
}
func TestConsumeGiftEventIgnoresNormalGiftWithoutRelationship(t *testing.T) {
schema := mysqlschema.New(t)
repo := New(schema.DB)
ctx := appcode.WithContext(context.Background(), appcode.Default)
nowMS := int64(1700000000000)
seedCPTestUser(t, schema.DB, 4301, "No CP Alice", nowMS)
seedCPTestUser(t, schema.DB, 4302, "No CP Bob", nowMS)
result, err := repo.ConsumeGiftEvent(ctx, cpdomain.GiftEvent{
AppCode: appcode.Default,
EventID: "room_evt_normal_no_cp",
RoomID: "room-normal",
RoomVersion: 3,
OccurredAtMS: nowMS + 1,
SenderUserID: 4301,
TargetUserID: 4302,
GiftID: "rose",
GiftCount: 1,
GiftValue: 37,
BillingReceiptID: "receipt-normal-no-cp",
CommandID: "cmd-normal-no-cp",
GiftTypeCode: "normal",
}, nowMS+2)
if err != nil {
t.Fatalf("consume normal gift without cp failed: %v", err)
}
if result.Consumed {
t.Fatalf("normal gift without active relationship must be ignored: %+v", result)
}
var applicationCount int64
if err := schema.DB.QueryRowContext(ctx, `
SELECT COUNT(*)
FROM user_cp_applications
WHERE app_code = ? AND requester_user_id = ? AND target_user_id = ?`,
appcode.Default, 4301, 4302,
).Scan(&applicationCount); err != nil {
t.Fatalf("count cp applications failed: %v", err)
}
if applicationCount != 0 {
t.Fatalf("normal gift without relationship must not create application, got %d", applicationCount)
}
}
func TestBreakRelationshipPrepareConfirmIsIdempotent(t *testing.T) {
schema := mysqlschema.New(t)
repo := New(schema.DB)