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
+54 -13
View File
@@ -38,6 +38,11 @@ func EnsureListingConversation(tx *gorm.DB, listing model.RentalListing, preferr
return nil, err
}
siteID, err := resolveListingDistributionSiteID(tx, listing)
if err != nil {
return nil, err
}
// 2. 建会话
now := time.Now()
conversation := model.ChatConversation{
@@ -46,6 +51,9 @@ func EnsureListingConversation(tx *gorm.DB, listing model.RentalListing, preferr
Title: listingConversationTitle(listing),
Status: "active",
}
if siteID > 0 {
conversation.DistributionSiteID = &siteID
}
if err := tx.Create(&conversation).Error; err != nil {
return nil, err
}
@@ -88,17 +96,12 @@ func EnsureListingConversation(tx *gorm.DB, listing model.RentalListing, preferr
}
}
// 4. 取二维码(带行锁)
var qrcode *model.ChatQrCode
var qrcodeImageURL string
qrcodeObj, err := fetchUnusedQrCodeForListing(tx, listing.ID)
// 4. 取二维码:分站优先用站点固定码(可复用);否则消耗对应码池。
qrcode, qrcodeImageURL, err := resolveListingQrCode(tx, siteID)
if err != nil {
return nil, err
}
if qrcodeObj != nil {
qrcode = qrcodeObj
qrcodeImageURL = qrcode.ImageURL
// 标记二维码为已使用
if qrcode != nil {
if err := markQrCodeAsUsed(tx, qrcode.ID, conversation.ID); err != nil {
return nil, err
}
@@ -309,22 +312,60 @@ func fetchUnusedQrCode(tx *gorm.DB) (*model.ChatQrCode, error) {
return fetchUnusedQrCodeForSite(tx, nil)
}
func fetchUnusedQrCodeForListing(tx *gorm.DB, listingID uint64) (*model.ChatQrCode, error) {
func resolveListingDistributionSiteID(tx *gorm.DB, listing model.RentalListing) (uint64, error) {
if listing.DistributionSiteID != nil && *listing.DistributionSiteID > 0 {
return *listing.DistributionSiteID, nil
}
if listing.ID == 0 {
return 0, nil
}
var relation model.DistributionSiteAccount
// 仅匹配启用中的分销站点,真实 DB 错误直接返回,避免误回退到全局二维码池。
err := tx.Table("distribution_site_accounts AS relation").
Select("relation.*").
Joins("JOIN distribution_sites AS site ON site.id = relation.site_id AND site.status = 'active'").
Where("relation.listing_id = ? AND relation.display_status = 'enabled'", listingID).
Where("relation.listing_id = ? AND relation.display_status = 'enabled'", listing.ID).
Order("relation.id ASC").
Limit(1).
Scan(&relation).Error
if err != nil {
return nil, err
return 0, err
}
if relation.ID != 0 {
return fetchUnusedQrCodeForSite(tx, &relation.SiteID)
return relation.SiteID, nil
}
return fetchUnusedQrCodeForSite(tx, nil)
return 0, nil
}
func resolveListingQrCode(tx *gorm.DB, siteID uint64) (*model.ChatQrCode, string, error) {
if siteID > 0 {
var site model.DistributionSite
err := tx.Select("id", "listing_qrcode_url").First(&site, siteID).Error
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return nil, "", err
}
if err == nil {
if url := strings.TrimSpace(site.ListingQrcodeURL); url != "" {
return nil, url, nil
}
}
qrcode, err := fetchUnusedQrCodeForSite(tx, &siteID)
if err != nil {
return nil, "", err
}
if qrcode != nil {
return qrcode, qrcode.ImageURL, nil
}
return nil, "", nil
}
qrcode, err := fetchUnusedQrCodeForSite(tx, nil)
if err != nil {
return nil, "", err
}
if qrcode != nil {
return qrcode, qrcode.ImageURL, nil
}
return nil, "", nil
}
func fetchUnusedQrCodeForSite(tx *gorm.DB, siteID *uint64) (*model.ChatQrCode, error) {