feat(distribution): 支持分站发布群固定二维码

主站分销配置可为每个站点绑定可复用企微码,号主在该站上传账号后发到对应发布群,不再消耗主站码池。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
yml2213
2026-09-15 19:02:10 +08:00
co-authored by Cursor
parent 776c33f83c
commit 1c86bef765
9 changed files with 397 additions and 16 deletions
@@ -479,3 +479,145 @@ func TestBatchCreateQrCodeDeliversPendingTask(t *testing.T) {
t.Fatalf("未生成二维码图片消息: %v", err)
}
}
func TestEnsureListingConversationUsesSiteFixedQrcode(t *testing.T) {
db := setupQrCodeTestDB(t)
site := model.DistributionSite{
SiteCode: "fx_qr_fixed",
SiteName: "固定码分站",
Domain: "fixed-qr.example.com",
Status: "active",
ListingQrcodeURL: "/api/files/object?key=qrcode/site-fixed.png",
}
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-pool.png", Status: QrCodeStatusUnused},
{ImageURL: "/api/files/object?key=qrcode/site-pool.png", Status: QrCodeStatusUnused, DistributionSiteID: &siteID},
}
if err := db.Create(&pool).Error; err != nil {
t.Fatalf("创建码池失败: %v", err)
}
listing := model.RentalListing{
ID: 202,
ListingNo: "L202609150001",
OwnerID: 10,
DistributionSiteID: &siteID,
}
var conv *model.ChatConversation
if err := db.Transaction(func(tx *gorm.DB) error {
created, err := EnsureListingConversation(tx, listing, 0)
conv = created
return err
}); err != nil {
t.Fatalf("建发布群失败: %v", err)
}
if conv == nil || conv.DistributionSiteID == nil || *conv.DistributionSiteID != siteID {
t.Fatalf("发布群站点 = %v, want %d", conv.DistributionSiteID, siteID)
}
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)
}
if !strings.Contains(string(message.AttachmentURLS), "site-fixed.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)
}
if unusedCount != 2 {
t.Fatalf("固定码仍应保留码池库存, unused = %d, want 2", unusedCount)
}
var taskCount int64
if err := db.Model(&model.ChatQrCodeDeliveryTask{}).Count(&taskCount).Error; err != nil {
t.Fatalf("统计补发任务失败: %v", err)
}
if taskCount != 0 {
t.Fatalf("固定码场景不应创建补发任务, got %d", taskCount)
}
listing2 := model.RentalListing{
ID: 203,
ListingNo: "L202609150002",
OwnerID: 11,
DistributionSiteID: &siteID,
}
if err := db.Transaction(func(tx *gorm.DB) error {
_, err := EnsureListingConversation(tx, listing2, 0)
return err
}); 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 unusedCount != 2 {
t.Fatalf("同一分站固定码应可复用, unused = %d, want 2", unusedCount)
}
}
func TestEnsureListingConversationSiteWithoutFixedQrUsesSitePool(t *testing.T) {
db := setupQrCodeTestDB(t)
site := model.DistributionSite{
SiteCode: "fx_qr_pool",
SiteName: "码池分站",
Domain: "pool-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-pool.png", Status: QrCodeStatusUnused},
{ImageURL: "/api/files/object?key=qrcode/site-pool.png", Status: QrCodeStatusUnused, DistributionSiteID: &siteID},
}
if err := db.Create(&pool).Error; err != nil {
t.Fatalf("创建码池失败: %v", err)
}
listing := model.RentalListing{
ID: 204,
ListingNo: "L202609150003",
OwnerID: 12,
DistributionSiteID: &siteID,
}
var conv *model.ChatConversation
if err := db.Transaction(func(tx *gorm.DB) error {
created, err := EnsureListingConversation(tx, listing, 0)
conv = created
return err
}); err != nil {
t.Fatalf("建发布群失败: %v", err)
}
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)
}
if !strings.Contains(string(message.AttachmentURLS), "site-pool.png") {
t.Fatalf("二维码附件 = %s, want 分站码池", message.AttachmentURLS)
}
var siteQR model.ChatQrCode
if err := db.First(&siteQR, pool[1].ID).Error; err != nil {
t.Fatalf("查询分站码失败: %v", err)
}
if siteQR.Status != QrCodeStatusUsed {
t.Fatalf("未配置固定码时应消耗分站码池, status = %q", siteQR.Status)
}
var mainQR model.ChatQrCode
if err := db.First(&mainQR, pool[0].ID).Error; err != nil {
t.Fatalf("查询主站码失败: %v", err)
}
if mainQR.Status != QrCodeStatusUnused {
t.Fatalf("分站上架不得消耗主站码池, status = %q", mainQR.Status)
}
}