// 功能描述:用户商品收藏、分类列表与条件状态更新;版本:1.0.0。 package user import ( "errors" "fmt" "math" "git.apinb.com/bsm-sdk/core/errcode" "git.apinb.com/bsm-sdk/core/infra" "git.apinb.com/heqiapp/platforms/backend/api/internal/impl" common "git.apinb.com/heqiapp/platforms/backend/api/internal/logic/common" "git.apinb.com/heqiapp/platforms/backend/api/internal/models" "github.com/gin-gonic/gin" "gorm.io/gorm" ) var errFavoriteChanged = errcode.NewError(2404, "收藏状态已变化,请刷新后重试") // favoriteValue 只返回商品公开标识和收藏状态,版本绑定收藏关系以防旧请求复活。 func favoriteValue(item models.EcFavorite, productIdentity string) gin.H { revision := "" if item.ID != 0 { revision = fmt.Sprintf("%s:%d", item.Identity, item.Revision) } return gin.H{"product_identity": productIdentity, "favorite": item.ID != 0 && item.Status == common.StatusEnable, "revision": revision} } // FavoriteState 读取当前账户对指定商品的状态;商品下架后仍能取消收藏。 func FavoriteState(ctx *gin.Context) { account, ok := common.UserAccount(ctx) if !ok { return } var product models.EcProduct if err := impl.DBService.Unscoped().Where("identity = ?", ctx.Param("identity")).Take(&product).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { err = errcode.ErrRecordNotFound } infra.Response.Error(ctx, err) return } var item models.EcFavorite if err := impl.DBService.Where("user_account_id = ? AND ec_product_id = ?", account.ID, product.ID).Find(&item).Error; err != nil { infra.Response.Error(ctx, err) return } infra.Response.Success(ctx, favoriteValue(item, product.Identity)) } // SetFavorite 使用绝对收藏状态和原版本;相同目标重试无写入,不支持物理删除。 func SetFavorite(ctx *gin.Context) { account, ok := common.UserAccount(ctx) if !ok { return } var request struct { Favorite *bool `json:"favorite" binding:"required"` Revision string `json:"revision" binding:"max=128"` } if ctx.ShouldBindJSON(&request) != nil { infra.Response.Error(ctx, errcode.ErrInvalidArgument) return } var item models.EcFavorite err := impl.DBService.Transaction(func(tx *gorm.DB) error { if err := lockAddressOwner(tx, account.ID); err != nil { return err } var product models.EcProduct if err := tx.Unscoped().Where("identity = ?", ctx.Param("identity")).Take(&product).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return errcode.ErrRecordNotFound } return err } if err := tx.Where("user_account_id = ? AND ec_product_id = ?", account.ID, product.ID).Find(&item).Error; err != nil { return err } state := favoriteValue(item, product.Identity) if state["favorite"] == *request.Favorite { return nil } if state["revision"] != request.Revision { return errFavoriteChanged } if *request.Favorite && (product.Status != common.StatusEnable || product.DeletedAt.Valid) { return errcode.ErrRecordNotFound } if *request.Favorite { var count int64 if err := tx.Model(&models.EcFavorite{}).Where("user_account_id = ? AND status = ?", account.ID, common.StatusEnable).Count(&count).Error; err != nil { return err } if count >= 1000 { return errcode.ErrOutOfRange } } if item.ID == 0 { item = models.EcFavorite{Entity: common.NewEntity(common.StatusEnable), UserAccountID: account.ID, EcProductID: product.ID, Revision: 1} return tx.Create(&item).Error } if item.Revision >= math.MaxInt64 { return errcode.ErrOutOfRange } status := common.StatusArchived if *request.Favorite { status = common.StatusEnable } return tx.Model(&item).Updates(map[string]any{"status": status, "revision": item.Revision + 1}).Error }) if err != nil { infra.Response.Error(ctx, err) return } infra.Response.Success(ctx, favoriteValue(item, ctx.Param("identity"))) } // ListFavorites 按最后状态更新时间分页;下架/软删除商品仍展示历史条目并禁止加购。 func ListFavorites(ctx *gin.Context) { account, ok := common.UserAccount(ctx) if !ok { return } query, page, ok := paginateClientList(ctx, impl.DBService.Where("user_account_id = ? AND status = ?", account.ID, common.StatusEnable)) if !ok { return } var items []models.EcFavorite if err := query.Order("updated_at desc, identity").Find(&items).Error; err != nil { infra.Response.Error(ctx, err) return } result, err := favoriteRows(impl.DBService, items) if err != nil { infra.Response.Error(ctx, err) return } respondClientPage(ctx, page, result) }