feat(distribution): 分站发布群只使用自己的二维码池

主站单独维护分站码池,池空时不再借用主站或其他分站,并去掉已停用的固定二维码入口。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
yml2213
2026-10-04 18:35:34 +08:00
co-authored by Cursor
parent 951d428364
commit 1d21b3f548
11 changed files with 1123 additions and 250 deletions
+115 -19
View File
@@ -480,7 +480,7 @@ func TestBatchCreateQrCodeDeliversPendingTask(t *testing.T) {
}
}
func TestEnsureListingConversationUsesSiteFixedQrcode(t *testing.T) {
func TestEnsureListingConversationIgnoresFixedQrAndUsesSitePool(t *testing.T) {
db := setupQrCodeTestDB(t)
site := model.DistributionSite{
SiteCode: "fx_qr_fixed",
@@ -521,26 +521,25 @@ func TestEnsureListingConversationUsesSiteFixedQrcode(t *testing.T) {
var message model.ChatMessage
if err := db.Where("conversation_id = ? AND content_type = ?", conv.ID, "image").First(&message).Error; err != nil {
t.Fatalf("未发送固定二维码: %v", err)
t.Fatalf("未发送分站池二维码: %v", err)
}
if !strings.Contains(string(message.AttachmentURLS), "site-fixed.png") {
t.Fatalf("二维码附件 = %s, want 分站固定码", message.AttachmentURLS)
if strings.Contains(string(message.AttachmentURLS), "site-fixed.png") || !strings.Contains(string(message.AttachmentURLS), "site-pool.png") {
t.Fatalf("二维码附件 = %s, want 分站码池而不是固定图", message.AttachmentURLS)
}
var unusedCount int64
if err := db.Model(&model.ChatQrCode{}).Where("status = ?", QrCodeStatusUnused).Count(&unusedCount).Error; err != nil {
t.Fatalf("统计码池失败: %v", err)
var siteQR model.ChatQrCode
if err := db.Where("distribution_site_id = ?", siteID).First(&siteQR).Error; err != nil {
t.Fatalf("查询分站码失败: %v", err)
}
if unusedCount != 2 {
t.Fatalf("固定码仍应保留码池库存, unused = %d, want 2", unusedCount)
if siteQR.Status != QrCodeStatusUsed {
t.Fatalf("分站码应被消耗, status = %q", siteQR.Status)
}
var taskCount int64
if err := db.Model(&model.ChatQrCodeDeliveryTask{}).Count(&taskCount).Error; err != nil {
t.Fatalf("统计补发任务失败: %v", err)
var mainQR model.ChatQrCode
if err := db.Where("distribution_site_id IS NULL").First(&mainQR).Error; err != nil {
t.Fatalf("查询主站码失败: %v", err)
}
if taskCount != 0 {
t.Fatalf("固定码场景不应创建补发任务, got %d", taskCount)
if mainQR.Status != QrCodeStatusUnused {
t.Fatalf("分站建群不得消耗主站码, status = %q", mainQR.Status)
}
listing2 := model.RentalListing{
@@ -555,11 +554,18 @@ func TestEnsureListingConversationUsesSiteFixedQrcode(t *testing.T) {
}); err != nil {
t.Fatalf("第二次建发布群失败: %v", err)
}
if err := db.Model(&model.ChatQrCode{}).Where("status = ?", QrCodeStatusUnused).Count(&unusedCount).Error; err != nil {
t.Fatalf("再次统计码池失败: %v", err)
if err := db.First(&mainQR, mainQR.ID).Error; err != nil {
t.Fatalf("再次查询主站码失败: %v", err)
}
if unusedCount != 2 {
t.Fatalf("同一分站固定码应可复用, unused = %d, want 2", unusedCount)
if mainQR.Status != QrCodeStatusUnused {
t.Fatalf("分站池空了仍不得借用主站码, status = %q", mainQR.Status)
}
var taskCount int64
if err := db.Model(&model.ChatQrCodeDeliveryTask{}).Where("status = ?", "pending").Count(&taskCount).Error; err != nil {
t.Fatalf("统计补发任务失败: %v", err)
}
if taskCount != 1 {
t.Fatalf("分站池空时应留下补发任务, got %d", taskCount)
}
}
@@ -621,3 +627,93 @@ func TestEnsureListingConversationSiteWithoutFixedQrUsesSitePool(t *testing.T) {
t.Fatalf("分站上架不得消耗主站码池, status = %q", mainQR.Status)
}
}
func TestListQrCodesExcludesSitePool(t *testing.T) {
db := setupQrCodeTestDB(t)
repo := NewRepository(db, nil, nil)
site := model.DistributionSite{
SiteCode: "fx_qr_hidden",
SiteName: "隐藏码池站",
Domain: "hidden-qr.example.com",
Status: "active",
}
if err := db.Create(&site).Error; err != nil {
t.Fatalf("创建分销站点失败: %v", err)
}
siteID := site.ID
pool := []model.ChatQrCode{
{ImageURL: "/api/files/object?key=qrcode/main-only.png", Status: QrCodeStatusUnused, GroupName: "主站群"},
{ImageURL: "/api/files/object?key=qrcode/site-only.png", Status: QrCodeStatusUnused, GroupName: "分站群", DistributionSiteID: &siteID},
}
if err := db.Create(&pool).Error; err != nil {
t.Fatalf("创建码池失败: %v", err)
}
items, total, err := repo.ListQrCodes(t.Context(), QrCodeListRequest{Page: 1, Limit: 20})
if err != nil {
t.Fatalf("查询主站码池失败: %v", err)
}
if total != 1 || len(items) != 1 || items[0].GroupName != "主站群" {
t.Fatalf("主站码池 = %+v total %d, want 只有主站群", items, total)
}
siteItems, siteTotal, err := repo.ListSiteQrCodes(t.Context(), siteID, QrCodeListRequest{Page: 1, Limit: 20})
if err != nil {
t.Fatalf("查询分站码池失败: %v", err)
}
if siteTotal != 1 || len(siteItems) != 1 || siteItems[0].GroupName != "分站群" {
t.Fatalf("分站码池 = %+v total %d, want 只有分站群", siteItems, siteTotal)
}
}
func TestDeliverPendingUsesOwnSitePool(t *testing.T) {
db := setupQrCodeTestDB(t)
repo := NewRepository(db, nil, nil)
site := model.DistributionSite{
SiteCode: "fx_qr_pending",
SiteName: "待补发站",
Domain: "pending-qr.example.com",
Status: "active",
}
if err := db.Create(&site).Error; err != nil {
t.Fatalf("创建分销站点失败: %v", err)
}
siteID := site.ID
conversation := model.ChatConversation{
Type: ConversationTypeListingGroup,
Title: "分站发布群",
Status: "active",
DistributionSiteID: &siteID,
}
if err := db.Create(&conversation).Error; err != nil {
t.Fatalf("创建群聊失败: %v", err)
}
task := model.ChatQrCodeDeliveryTask{ConversationID: conversation.ID, Status: "pending"}
if err := db.Create(&task).Error; err != nil {
t.Fatalf("创建补发任务失败: %v", err)
}
mainQR := model.ChatQrCode{ImageURL: "/api/files/object?key=qrcode/main-pending.png", Status: QrCodeStatusUnused}
if err := db.Create(&mainQR).Error; err != nil {
t.Fatalf("创建主站码失败: %v", err)
}
if _, err := repo.BatchCreateSiteQrCode(t.Context(), 1, siteID, BatchCreateQrCodeRequest{
Items: []CreateQrCodeRequest{{ImageURL: "/api/files/object?key=qrcode/site-pending.png", GroupName: "分站补发"}},
}); err != nil {
t.Fatalf("补充分站码失败: %v", err)
}
var savedTask model.ChatQrCodeDeliveryTask
if err := db.First(&savedTask, task.ID).Error; err != nil {
t.Fatalf("查询补发任务失败: %v", err)
}
if savedTask.Status != "sent" {
t.Fatalf("分站补发任务状态 = %q, want sent", savedTask.Status)
}
if err := db.First(&mainQR, mainQR.ID).Error; err != nil {
t.Fatalf("查询主站码失败: %v", err)
}
if mainQR.Status != QrCodeStatusUnused {
t.Fatalf("补发分站群不得消耗主站码, status = %q", mainQR.Status)
}
}