0
0
0

多用户社交电商系统搭建:种草模块技术方案

职场区 3253582893 1月前
1794

源码获取:ym7k.com/18951/

多用户社交电商系统:种草模块的社区架构与粉丝经济工程实现

引子:种草不是内容功能,而是交易链路的重构

大多数商城系统把"种草"理解为一个内容模块:用户可以发图文、发视频、点赞评论。这个理解没有错,但它遗漏了社交电商最核心的东西——种草的价值不在于内容本身,而在于内容与商品、内容与创作者、创作者与粉丝之间的三重关系。

一条种草内容如果只是"好看",它和普通社交平台的内容没有区别。它的价值在于:内容关联了商品(可购买),创作者关联了粉丝(可关注),粉丝关联了交易(可转化)。这三重关系构成了社交电商的完整链路:内容吸引注意力 → 创作者建立信任 → 信任驱动交易。

多用户社交电商系统的种草模块,其工程挑战不在于实现一个内容社区,而在于把内容社区、用户关系、商品分销和佣金结算正确耦合在一起。这需要一套比普通商城更复杂的数据模型和业务逻辑。

本文从种草模块的数据模型、粉丝关系的建立与运营、内容与商品的分销绑定、佣金的多级结算,到社区内容的推荐与审核,展开这类系统的完整技术分析。

一、种草模块的数据模型:内容、关系、交易三张网

1.1 三张网的交叉结构

种草模块的数据模型可以抽象为三张网。

内容网。 种草内容(图文/视频)、评论、点赞、收藏、话题。这是内容的组织维度。

关系网。 关注关系、粉丝关系、好友关系、社群关系。这是人的组织维度。

交易网。 商品、订单、分销关系、佣金记录。这是钱的组织维度。

三张网的交点是内容与商品的关联和创作者与粉丝的关联。种草模块的工程复杂度,主要来自这两组关联的正确维护。

1.2 内容表的设计

sql
CREATE TABLE `post` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL COMMENT '发布者',
  `type` tinyint(1) NOT NULL DEFAULT '1' COMMENT '1=图文 2=视频 3=买家秀',
  `category` varchar(20) NOT NULL DEFAULT 'grass' COMMENT 'grass=种草 new=上新 show=买家秀',
  `title` varchar(200) DEFAULT NULL,
  `content` text COMMENT '正文',
  `images` text COMMENT '图片列表JSON',
  `video_url` varchar(500) DEFAULT NULL,
  `video_cover` varchar(255) DEFAULT NULL,
  `topics` varchar(500) DEFAULT NULL COMMENT '话题标签',
  `location` varchar(100) DEFAULT NULL COMMENT '位置',
  `view_count` int(11) NOT NULL DEFAULT '0',
  `like_count` int(11) NOT NULL DEFAULT '0',
  `collect_count` int(11) NOT NULL DEFAULT '0',
  `comment_count` int(11) NOT NULL DEFAULT '0',
  `share_count` int(11) NOT NULL DEFAULT '0',
  `order_count` int(11) NOT NULL DEFAULT '0' COMMENT '引导下单数',
  `gmv` decimal(12,2) NOT NULL DEFAULT '0.00' COMMENT '引导成交额',
  `status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '0=待审核 1=已发布 2=拒绝 3=下架',
  `published_at` datetime DEFAULT NULL,
  `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_user` (`user_id`),
  KEY `idx_category_status` (`category`, `status`),
  KEY `idx_published` (`published_at`),
  KEY `idx_topics` (`topics`(191))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

category 字段区分了种草内容的三种形态:grass(种草) 是推荐型内容,new(上新) 是商家或创作者发布的新品内容,show(买家秀) 是买家购买后的真实分享。三种形态的内容在推荐策略和佣金规则上有所不同。

order_count 和 gmv 字段是社交电商特有的。它们记录了这条内容直接引导的订单数和成交额。这两个字段是创作者佣金结算的依据,也是内容推荐的重要信号。

1.3 内容与商品的关联

一条种草内容可以关联多个商品。这个关联不是简单的字段,而是一张独立的关系表。

sql
CREATE TABLE `post_product` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `post_id` bigint(20) NOT NULL,
  `product_id` int(11) NOT NULL,
  `merchant_id` int(11) DEFAULT NULL COMMENT '商品所属商户',
  `sort` tinyint(4) NOT NULL DEFAULT '0' COMMENT '展示顺序',
  `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_post_product` (`post_id`, `product_id`),
  KEY `idx_product` (`product_id`),
  KEY `idx_merchant` (`merchant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这张表的核心作用是支持双向查询:从内容查商品(展示时用),从商品查内容(详情页展示"相关种草"时用)。两个索引分别服务这两种查询模式。

1.4 分销关系的绑定

社交电商的种草模块与分销体系是深度耦合的。当用户通过某条种草内容进入商品并下单时,系统需要记录"这笔订单是由哪条内容、哪个创作者引导的"。

sql
CREATE TABLE `distribution_bind` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL COMMENT '被绑定用户',
  `distributor_id` int(11) NOT NULL COMMENT '分销商(创作者)',
  `post_id` bigint(20) DEFAULT NULL COMMENT '来源内容',
  `product_id` int(11) DEFAULT NULL COMMENT '来源商品',
  `bind_type` tinyint(1) NOT NULL DEFAULT '1' COMMENT '1=内容绑定 2=链接绑定 3=二维码绑定',
  `expire_at` datetime DEFAULT NULL COMMENT '绑定有效期',
  `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_user` (`user_id`),
  KEY `idx_distributor` (`distributor_id`),
  KEY `idx_post` (`post_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

distribution_bind 表记录了用户与分销商之间的绑定关系。这个绑定通常有有效期(如 15 天),在有效期内,用户的所有下单都归因于该分销商。

绑定逻辑的关键是"首次绑定优先"或"最后绑定优先" 。这两种策略各有优劣:首次绑定优先保护早期创作者的权益,最后绑定优先奖励最近一次的引导行为。系统需要提供配置选项,让商家选择策略。

二、粉丝经济:关系链的建立与运营

2.1 关注与粉丝关系

粉丝经济的基础是关注关系。用户关注创作者后,创作者发布的内容会出现在用户的关注流中。

sql
CREATE TABLE `user_follow` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL COMMENT '关注者',
  `target_id` int(11) NOT NULL COMMENT '被关注者',
  `relation` tinyint(1) NOT NULL DEFAULT '1' COMMENT '1=单向关注 2=互相关注',
  `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_follow` (`user_id`, `target_id`),
  KEY `idx_target` (`target_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

relation 字段记录关系的性质。当 A 关注 B,且 B 也关注 A 时,relation 从 1 变为 2(互相关注)。这个字段的存在使得"好友"和"关注"可以在同一张表中管理。

2.2 粉丝分层与运营

粉丝经济的高级形态是粉丝分层。不同的粉丝有不同的价值,系统需要区分对待。

sql
CREATE TABLE `user_fans_level` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL,
  `distributor_id` int(11) NOT NULL COMMENT '所属创作者',
  `level` tinyint(1) NOT NULL DEFAULT '1' COMMENT '1=普通粉丝 2=活跃粉丝 3=核心粉丝',
  `total_orders` int(11) NOT NULL DEFAULT '0' COMMENT '累计订单数',
  `total_amount` decimal(12,2) NOT NULL DEFAULT '0.00' COMMENT '累计消费额',
  `last_active_at` datetime DEFAULT NULL,
  `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_user_distributor` (`user_id`, `distributor_id`),
  KEY `idx_distributor_level` (`distributor_id`, `level`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

粉丝分层的规则可以由商家配置。例如:累计消费 1000 元以上为活跃粉丝,5000 元以上为核心粉丝。不同层级的粉丝可以看到不同的内容、享受不同的折扣、获得不同的服务。

2.3 内容发布的粉丝触达

当创作者发布内容时,系统需要推送给粉丝。这个推送不是无差别的,而是根据粉丝层级和活跃度进行筛选。

php
<?php
// app/common/service/FansNotifyService.php

namespace app\common\service;

use think\facade\Db;
use think\facade\Queue;

class FansNotifyService
{
    /**
     * 内容发布后通知粉丝
     */
    public function notifyFans(int $postId, int $authorId): void
    {
        $post = Db::name('post')->where('id', $postId)->find();
        if (!$post) return;

        // 分批获取粉丝(避免一次性加载过多)
        $batchSize = 1000;
        $offset = 0;

        while (true) {
            $fans = Db::name('user_follow')
                ->alias('f')
                ->join('user_fans_level fl', 'f.user_id = fl.user_id AND fl.distributor_id = f.target_id')
                ->where('f.target_id', $authorId)
                ->where('fl.level', '>=', 1)
                ->limit($offset, $batchSize)
                ->column('f.user_id, fl.level');

            if (empty($fans)) break;

            // 核心粉丝优先推送(立即推送)
            // 普通粉丝延迟推送(合并推送)
            foreach ($fans as $fan) {
                $delay = $fan['level'] >= 3 ? 0 : mt_rand(60, 600);
                Queue::push('app\job\NotifyFans', [
                    'user_id' => $fan['user_id'],
                    'post_id' => $postId,
                    'author_id' => $authorId,
                ], 'notification', $delay);
            }

            $offset += $batchSize;
        }
    }

    /**
     * 合并推送:同一创作者的多条内容合并为一条通知
     */
    public function mergeNotify(int $userId, int $authorId): void
    {
        $cacheKey = "notify:merge:{$userId}:{$authorId}";
        $count = Db::name('post')
            ->where('user_id', $authorId)
            ->where('status', 1)
            ->where('published_at', '>', date('Y-m-d H:i:s', strtotime('-1 hour')))
            ->count();

        if ($count > 1) {
            // 多条内容,合并为一条通知
            $message = "您关注的创作者发布了 {$count} 条新内容";
        } else {
            $message = "您关注的创作者发布了新内容";
        }

        // 发送合并通知
    }
}

核心粉丝立即推送,普通粉丝延迟合并推送——这是粉丝运营的常见策略。核心粉丝对创作者的忠诚度高,及时触达能提升互动率;普通粉丝如果被频繁打扰,可能会取关。

三、佣金结算:从内容到交易的归因

3.1 归因链路

社交电商的佣金结算,核心是归因:一笔订单的成交,应该归功于哪个创作者、哪条内容、哪个环节?

完整的归因链路是:

text
用户浏览种草内容 → 点击商品 → 进入商品详情 → 下单
    ↓
记录:post_id, product_id, distributor_id
    ↓
订单完成后 → 查找归因记录 → 计算佣金

归因的关键是在用户点击商品时记录来源。这个记录需要在用户进入商品详情页时保存,并在下单时携带到订单中。

php
<?php
// app/api/controller/PostController.php

namespace app\api\controller;

use app\common\service\DistributionBindService;

class PostController
{
    /**
     * 用户从种草内容点击商品
     */
    public function clickProduct()
    {
        $postId = input('post_id/d');
        $productId = input('product_id/d');
        $userId = $this->userId;

        $post = Db::name('post')->where('id', $postId)->find();
        if (!$post) {
            return json(['code' => 404, 'msg' => '内容不存在']);
        }

        // 记录归因绑定
        $bindService = new DistributionBindService();
        $bindService->bind($userId, $post['user_id'], [
            'post_id' => $postId,
            'product_id' => $productId,
            'bind_type' => 1,
        ]);

        // 记录行为
        Db::name('post_action')->insert([
            'post_id' => $postId,
            'user_id' => $userId,
            'action' => 'click_product',
            'product_id' => $productId,
            'created_at' => date('Y-m-d H:i:s'),
        ]);

        return json(['code' => 200]);
    }
}

3.2 佣金计算

订单完成后,系统根据归因记录计算佣金。

php
<?php
// app/common/service/CommissionService.php

namespace app\common\service;

use think\facade\Db;

class CommissionService
{
    /**
     * 订单完成后计算佣金
     */
    public function settle(int $orderId): void
    {
        $order = Db::name('order')->where('id', $orderId)->find();
        if (!$order || $order['status'] != 'completed') {
            return;
        }

        // 查找归因绑定
        $bind = Db::name('distribution_bind')
            ->where('user_id', $order['user_id'])
            ->where(function ($q) {
                $q->whereNull('expire_at')
                  ->whereOr('expire_at', '>', date('Y-m-d H:i:s'));
            })
            ->order('created_at', 'desc')
            ->find();

        if (!$bind) {
            return; // 无归因,不计算佣金
        }

        // 检查是否已结算(幂等)
        $exists = Db::name('commission')
            ->where('order_id', $orderId)
            ->count();
        if ($exists) {
            return;
        }

        // 获取分销商的分佣比例
        $distributor = Db::name('user')
            ->where('id', $bind['distributor_id'])
            ->find();

        $rate = $distributor['commission_rate'] ?? 5.00;

        // 计算佣金
        $commission = round($order['pay_amount'] * $rate / 100, 2);

        Db::startTrans();
        try {
            // 记录佣金
            Db::name('commission')->insert([
                'order_id' => $orderId,
                'user_id' => $bind['distributor_id'],
                'from_user_id' => $order['user_id'],
                'post_id' => $bind['post_id'],
                'product_id' => $bind['product_id'],
                'order_amount' => $order['pay_amount'],
                'rate' => $rate,
                'amount' => $commission,
                'status' => 0, // 待结算
                'created_at' => date('Y-m-d H:i:s'),
            ]);

            // 更新内容的引导数据
            if ($bind['post_id']) {
                Db::name('post')
                    ->where('id', $bind['post_id'])
                    ->inc('order_count', 1)
                    ->inc('gmv', $order['pay_amount'])
                    ->update();
            }

            Db::commit();

        } catch (\Exception $e) {
            Db::rollback();
            throw $e;
        }
    }

    /**
     * 佣金结算(定时任务或手动触发)
     */
    public function doSettle(int $commissionId): bool
    {
        Db::startTrans();
        try {
            $commission = Db::name('commission')
                ->where('id', $commissionId)
                ->where('status', 0)
                ->lock(true)
                ->find();

            if (!$commission) {
                throw new \Exception('佣金记录不存在或已结算');
            }

            // 更新佣金状态
            Db::name('commission')
                ->where('id', $commissionId)
                ->update([
                    'status' => 1,
                    'settled_at' => date('Y-m-d H:i:s'),
                ]);

            // 增加用户余额
            Db::name('user')
                ->where('id', $commission['user_id'])
                ->inc('balance', $commission['amount'])
                ->update();

            Db::commit();
            return true;

        } catch (\Exception $e) {
            Db::rollback();
            return false;
        }
    }
}

3.3 多级分销的佣金

社交电商通常支持多级分销。当用户 A 通过创作者 B 的内容下单时,B 获得一级佣金,B 的上级 C 获得二级佣金,以此类推。

多级佣金的计算需要向上追溯分销链。这个链的存储和查询方式,与前面文章中讨论的三级分销类似,使用 path 字段存储关系路径。

四、内容推荐:种草流的分发逻辑

4.1 种草流的推荐目标

种草流的推荐目标与普通内容社区不同。普通社区优化的是"停留时长"和"互动率",种草流优化的是"转化率"和"GMV"。

这意味着推荐算法需要平衡两个目标:内容质量(决定用户是否愿意看)和商业价值(决定用户是否愿意买)。

4.2 推荐信号的权重

php
<?php
// app/api/service/GrassFeedService.php

namespace app\api\service;

use think\facade\Db;
use think\facade\Cache;

class GrassFeedService
{
    /**
     * 种草流推荐
     */
    public function feed(int $userId, int $page, int $pageSize): array
    {
        $cacheKey = "grass_feed:{$userId}:{$page}";
        $cached = Cache::get($cacheKey);
        if ($cached) return $cached;

        // 1. 关注流(用户关注的创作者)
        $followed = $this->getFollowedPosts($userId, 10);

        // 2. 兴趣流(基于用户互动历史的标签匹配)
        $interest = $this->getInterestPosts($userId, 10);

        // 3. 高转化流(GMV 高的内容)
        $highConversion = $this->getHighConversionPosts(8);

        // 4. 新人流(新创作者扶持)
        $newcomer = $this->getNewcomerPosts(5);

        // 混合去重
        $merged = $this->mergeAndShuffle([
            $followed, $interest, $highConversion, $newcomer
        ]);

        // 分页
        $offset = ($page - 1) * $pageSize;
        $result = array_slice($merged, $offset, $pageSize);

        // 补充数据
        $result = $this->enrich($result, $userId);

        Cache::set($cacheKey, $result, 300);
        return $result;
    }

    /**
     * 高转化内容:GMV 和转化率加权
     */
    protected function getHighConversionPosts(int $limit): array
    {
        return Db::name('post')
            ->where('status', 1)
            ->where('category', 'grass')
            ->where('published_at', '>', date('Y-m-d H:i:s', strtotime('-14 days')))
            ->where('view_count', '>', 100) // 有足够曝光才计算转化率
            ->orderRaw('(gmv / GREATEST(view_count, 1)) * 0.6 + (order_count / GREATEST(view_count, 1)) * 1000 * 0.4 DESC')
            ->limit($limit)
            ->select()
            ->toArray();
    }

    /**
     * 兴趣内容:基于用户近期互动的标签
     */
    protected function getInterestPosts(int $userId, int $limit): array
    {
        // 获取用户近期互动内容的标签
        $tags = Db::name('post_action')
            ->alias('pa')
            ->join('post p', 'pa.post_id = p.id')
            ->where('pa.user_id', $userId)
            ->where('pa.created_at', '>', date('Y-m-d H:i:s', strtotime('-30 days')))
            ->whereIn('pa.action', ['like', 'collect', 'click_product'])
            ->column('p.topics');

        if (empty($tags)) {
            return [];
        }

        $tagCount = [];
        foreach ($tags as $tagStr) {
            foreach (explode(',', $tagStr ?? '') as $t) {
                $t = trim($t);
                if ($t) $tagCount[$t] = ($tagCount[$t] ?? 0) + 1;
            }
        }
        arsort($tagCount);
        $topTags = array_slice(array_keys($tagCount), 0, 8);

        if (empty($topTags)) return [];

        $conditions = array_map(function ($t) {
            return "FIND_IN_SET('{$t}', topics)";
        }, $topTags);

        return Db::name('post')
            ->where('status', 1)
            ->whereRaw('(' . implode(' OR ', $conditions) . ')')
            ->order('published_at', 'desc')
            ->limit($limit)
            ->select()
            ->toArray();
    }

    protected function getFollowedPosts(int $userId, int $limit): array
    {
        $followedIds = Db::name('user_follow')
            ->where('user_id', $userId)
            ->column('target_id');

        if (empty($followedIds)) return [];

        return Db::name('post')
            ->where('status', 1)
            ->whereIn('user_id', $followedIds)
            ->order('published_at', 'desc')
            ->limit($limit)
            ->select()
            ->toArray();
    }

    protected function getNewcomerPosts(int $limit): array
    {
        return Db::name('post')
            ->where('status', 1)
            ->where('published_at', '>', date('Y-m-d H:i:s', strtotime('-3 days')))
            ->where('view_count', '<', 500)
            ->order('published_at', 'desc')
            ->limit($limit)
            ->select()
            ->toArray();
    }

    protected function mergeAndShuffle(array $sources): array
    {
        $seen = [];
        $result = [];
        $maxLen = max(array_map('count', $sources));
        for ($i = 0; $i < $maxLen; $i++) {
            foreach ($sources as $source) {
                if (isset($source[$i]) && !isset($seen[$source[$i]['id']])) {
                    $seen[$source[$i]['id']] = true;
                    $result[] = $source[$i];
                }
            }
        }
        return $result;
    }

    protected function enrich(array $posts, int $userId): array
    {
        if (empty($posts)) return [];

        $postIds = array_column($posts, 'id');
        $authorIds = array_unique(array_column($posts, 'user_id'));

        // 作者信息
        $authors = Db::name('user')
            ->whereIn('id', $authorIds)
            ->field('id, nickname, avatar, is_verified, fans_count')
            ->select()
            ->toArray();
        $authorMap = array_column($authors, null, 'id');

        // 关注状态
        $followedIds = Db::name('user_follow')
            ->where('user_id', $userId)
            ->whereIn('target_id', $authorIds)
            ->column('target_id');
        $followedMap = array_flip($followedIds);

        // 点赞状态
        $likedIds = Db::name('post_like')
            ->where('user_id', $userId)
            ->whereIn('post_id', $postIds)
            ->column('post_id');
        $likedMap = array_flip($likedIds);

        // 关联商品
        $postProducts = Db::name('post_product')
            ->alias('pp')
            ->join('product p', 'pp.product_id = p.id')
            ->whereIn('pp.post_id', $postIds)
            ->field('pp.post_id, p.id, p.name, p.cover, p.price, p.sales')
            ->select()
            ->toArray();
        $productMap = [];
        foreach ($postProducts as $pp) {
            $productMap[$pp['post_id']][] = $pp;
        }

        foreach ($posts as &$post) {
            $post['author'] = $authorMap[$post['user_id']] ?? null;
            $post['is_followed'] = isset($followedMap[$post['user_id']]);
            $post['is_liked'] = isset($likedMap[$post['id']]);
            $post['products'] = $productMap[$post['id']] ?? [];
        }

        return $posts;
    }
}

这个推荐服务的核心设计是多源混合 + 商业信号加权。高转化内容的排序使用了 GMV 和转化率的复合公式,这是社交电商区别于普通内容社区的关键。

五、内容审核与合规

5.1 三层审核机制

社交电商的种草内容需要审核,因为它涉及商品推广,可能触及广告法、虚假宣传等合规问题。

第一层:机器审核。 调用内容安全 API 检测违规内容(涉政、涉黄、暴恐、违禁品)。

第二层:商品合规检查。 检查内容关联的商品是否有资质(如食品需要食品经营许可证,化妆品需要备案)。

第三层:人工审核。 对于机器无法判断的内容,进入人工审核队列。

php
<?php
// app/common/service/ContentAuditService.php

namespace app\common\service;

use think\facade\Db;
use think\facade\Log;

class ContentAuditService
{
    /**
     * 提交内容审核
     */
    public function audit(int $postId): array
    {
        $post = Db::name('post')->where('id', $postId)->find();
        if (!$post) {
            throw new \Exception('内容不存在');
        }

        $results = [
            'text' => $this->auditText($post['title'] . ' ' . $post['content']),
            'images' => $this->auditImages(json_decode($post['images'] ?? '[]', true)),
            'products' => $this->auditProducts($postId),
        ];

        // 汇总审核结果
        $passed = true;
        $reasons = [];

        foreach ($results as $type => $result) {
            if (!$result['passed']) {
                $passed = false;
                $reasons[] = $result['reason'];
            }
        }

        // 更新内容状态
        if ($passed) {
            Db::name('post')
                ->where('id', $postId)
                ->update([
                    'status' => 1,
                    'published_at' => date('Y-m-d H:i:s'),
                ]);
        } else {
            Db::name('post')
                ->where('id', $postId)
                ->update([
                    'status' => 2,
                    'reject_reason' => implode('; ', $reasons),
                ]);
        }

        return ['passed' => $passed, 'reasons' => $reasons];
    }

    /**
     * 文本审核
     */
    protected function auditText(string $text): array
    {
        // 调用内容安全 API
        $result = $this->callContentSecurityApi($text);

        if ($result['suggestion'] === 'pass') {
            return ['passed' => true];
        }

        return [
            'passed' => false,
            'reason' => '文本违规:' . ($result['label'] ?? '未知'),
        ];
    }

    /**
     * 图片审核
     */
    protected function auditImages(array $images): array
    {
        foreach ($images as $image) {
            $result = $this->callImageSecurityApi($image);
            if ($result['suggestion'] !== 'pass') {
                return [
                    'passed' => false,
                    'reason' => '图片违规',
                ];
            }
        }
        return ['passed' => true];
    }

    /**
     * 关联商品审核
     */
    protected function auditProducts(int $postId): array
    {
        $products = Db::name('post_product')
            ->alias('pp')
            ->join('product p', 'pp.product_id = p.id')
            ->where('pp.post_id', $postId)
            ->field('p.id, p.name, p.status, p.qualification')
            ->select()
            ->toArray();

        foreach ($products as $product) {
            if ($product['status'] != 1) {
                return [
                    'passed' => false,
                    'reason' => "商品「{$product['name']}」已下架",
                ];
            }
            // 检查商品资质
            if (!$this->checkProductQualification($product)) {
                return [
                    'passed' => false,
                    'reason' => "商品「{$product['name']}」资质不全",
                ];
            }
        }

        return ['passed' => true];
    }

    protected function checkProductQualification(array $product): bool
    {
        // 根据商品类目检查所需资质
        return true;
    }

    protected function callContentSecurityApi(string $text): array
    {
        // 调用内容安全 API
        return ['suggestion' => 'pass'];
    }

    protected function callImageSecurityApi(string $image): array
    {
        return ['suggestion' => 'pass'];
    }
}

5.2 广告合规

种草内容涉及商品推荐,需要符合《广告法》的要求。系统需要在几个层面做合规检查。

禁用词检查。 "最"、"第一"、"国家级"等极限词不能出现在内容中。

功效宣称检查。 化妆品、保健品等特殊商品不能宣称治疗功效。

价格对比检查。 "原价"、"划线价"需要有依据,不能虚构。

这些检查可以在内容审核时自动执行,违规内容直接拒绝或提示修改。

六、边界与张力

多用户社交电商系统的种草模块在工程实践中,有几个需要清醒认识的边界。

第一,归因的准确性挑战。 用户可能先看了 A 的内容,又看了 B 的内容,最后通过搜索下单。这笔订单应该归功于谁?系统需要明确归因规则(首次点击、最后点击、时间窗口),并在用户协议中说明。归因争议是社交电商的常见纠纷来源。

第二,内容的商业化与真实性的平衡。 种草内容本质上是商业推广,但用户期望它是真实的分享。如果内容过度商业化,用户会失去信任;如果完全不商业化,创作者没有动力。系统需要在产品层面设计激励机制,鼓励真实分享。

第三,多级分销的合规风险。 多级分销在某些司法管辖区可能触及传销的法律边界。系统需要提供分销层级的配置(1-3 级可调),并确保佣金来源于真实的商品销售,而非"拉人头"。

第四,内容审核的成本。 UGC 内容需要审核,人工审核的成本随内容量线性增长。对于中小型平台,内容审核可能成为运营成本的主要部分。机器审核可以降低成本,但准确率有限。

第五,粉丝关系的真实性与质量。 社交电商系统中,粉丝数量可能被刷单、买粉等行为污染。系统需要识别和过滤虚假粉丝,否则粉丝经济的价值会被稀释。

结语:种草模块的工程本质是"关系链的变现"

多用户社交电商系统的种草模块,其工程本质可以归结为一句话:把内容、关系和交易三张网正确耦合,让创作者的影响力可以量化和变现。

内容网提供吸引力,关系网提供信任,交易网提供变现路径。三者的交点是 post_product(内容关联商品)和 distribution_bind(用户绑定创作者)。这两张表的设计质量,决定了整个种草模块的工程可靠性。

粉丝经济不是"有粉丝就能赚钱",而是"粉丝的信任可以转化为交易,交易可以量化为佣金"。系统的任务是让这个转化过程可追踪、可计算、可结算。从内容发布到用户点击,从点击到下单,从下单到佣金结算,每一个环节都需要精确的记录和正确的计算。

这套系统的价值,最终体现在一个朴素的判断上:一个创作者发布了一条种草内容,系统能否准确告诉他,这条内容带来了多少曝光、多少点击、多少订单、多少佣金? 如果答案是肯定的,那它就是一个合格的社交电商种草系统;如果答案是否定的,那它只是一个内容社区。

最新回复 (0)

    暂无评论

请先登录后发表评论!

返回
请先登录后发表评论!