CRMEB Pro + AI 商品运营:怎么让 AI 生成标题、卖点和标签,但不乱改商品数据?

摘要

很多商城接 AI,第一反应是"让 AI 自动改商品标题、详情、标签"。这个方向看起来很爽,但在 CRMEB Pro 这种商品、SKU、标签、缓存、活动和移动端展示互相牵动的系统里,最危险的不是 AI 生成不出来,而是 AI 生成后直接写库,把商品主表、规格、标签和前台展示一起带乱。

这篇围绕一个很实用的小功能:AI 商品运营助手。目标不是让 AI 接管商品,而是让 AI 生成"候选标题、候选简介、候选规格、候选标签建议",运营确认后再走 CRMEB Pro 原有商品保存链路。

1. 这个项目里已经有 AI 商品能力

后台 AI 入口在:

text 复制代码
route/admin.php
app/controller/admin/v1/system/Ai.php
app/services/system/AiServices.php

路由里已经注册了 AI 接口:

php 复制代码
Route::post('chat_ai', 'v1.system.Ai/chatAi')
    ->option(['real_name' => '聊天AI接口']);

Controller 只负责接收参数、调用 Service、返回统一格式:

php 复制代码
class Ai extends AuthController
{
    #[Inject]
    protected AiServices $services;

    public function chatAi()
    {
        $data = $this->request->postMore([
            ['message', ''],
            ['store_name', ''],
            ['product_id', 0],
            ['unique', ''],
            ['type', '']
        ]);

        $result = $this->services->AiType($data);

        if ($result) {
            return $this->success($result);
        } else {
            return $this->fail('生成失败');
        }
    }
}

这个入口很适合继续扩展商品运营场景,因为它符合项目已有分层:Controller -> Services,并且返回格式也沿用后台标准。

2. AI 类型分发已经覆盖商品运营的核心素材

AiServices::AiType() 里已经有多个商品相关类型:

php 复制代码
public function AiType($data)
{
    switch ($data['type']) {
        case 'product_name':
            $result = $this->productName($data);
            break;
        case 'product_info':
            $result = $this->productInfo($data);
            break;
        case 'share_content':
            $result = $this->shareContent($data);
            break;
        case 'product_attr':
            $result = $this->productAttr($data);
            break;
        case 'product_specs':
            $result = $this->productSpecs($data);
            break;
        case 'product_reply':
            $result = $this->productReply($data);
            break;
        default:
            return '暂不支持该类型';
    }

    return $result;
}

这说明二开时不需要从零搭 AI 架构,可以优先沿用现有类型分发,再补充新的运营类型,例如:

text 复制代码
product_selling_points  商品卖点建议
product_tag_suggest     商品标签建议
product_seo_check       标题和简介风险检查
product_publish_draft   发布前运营草稿

注意:新增类型建议仍放在 AiServices 或同模块 Service 里,不要直接在 Controller 拼 prompt。

3. 为什么不能让 AI 直接保存商品

商品保存入口在:

text 复制代码
app/controller/admin/v1/product/StoreProduct.php
app/services/product/product/StoreProductServices.php

后台保存商品时,Controller 会把字段取出来,然后交给商品 Service:

php 复制代码
public function save(StoreProductAttrServices $attrServices, $id)
{
    $data = $this->request->postMore($this->getProductSaveFields());

    if ($this->supplierId != 0) {
        $data['supplier_id'] = $this->supplierId;
        if ($id && !$this->checkSupplierProductAuth((int)$id)) {
            return $this->fail('商品不存在或无权限');
        }
    }

    $admin_id = $this->adminId;
    $this->service->saveData((int)$id, $data, 0, 0, $admin_id);

    $this->service->cacheTag()->clear();
    $attrServices->cacheTag()->clear();

    return $this->success($id ? '保存商品信息成功' : '添加商品成功!');
}

真正复杂的是 StoreProductServices::saveData()。它不是简单更新 store_name

php 复制代码
public function saveData(int $id, array $data, int $type = 0, int $relation_id = 0, int $admin_id = 0)
{
    [$data, $detail, $attr, $description, $is_copy, $relationData, $slider_image]
        = $this->prepareProductSaveData($data, $type, $relation_id);

    [$skuList, $id, $is_new, $data] = $this->transaction(function () use (
        $id,
        $relationData,
        $data,
        $description,
        $storeDescriptionServices,
        $storeProductAttrServices,
        $detail,
        $attr,
        $storeDiscountProduct,
        $productVirtual,
        $slider_image
    ) {
        if ($id) {
            $this->setShow([$id], $data['is_show']);
            $oldInfo = $this->get($id)->toArray();

            if ($oldInfo['product_type'] != $data['product_type']) {
                throw new AdminException('商品类型不能切换!');
            }

            unset($data['sales']);
            $res = $this->dao->update($id, $data);
            if (!$res) throw new AdminException('修改失败');
        } else {
            $data['star'] = config('admin.product_default_star');
            $data['add_time'] = time();
            $data['code_path'] = '';
            $data['spu'] = $this->createSpu();

            $res = $this->dao->save($data);
            if (!$res) throw new AdminException('添加失败');
            $id = (int)$res->id;
        }

        $storeDescriptionServices->saveDescription($id, $description);
        $skuList = $storeProductAttrServices->validateProductAttr($attr, $detail, $id);
        $valueGroup = $storeProductAttrServices->saveProductAttr($skuList, $id);
        if (!$valueGroup) throw new AdminException('添加失败!');

        $attrStockArr = array_column($valueGroup, 'stock');
        $this->dao->update($id, [
            'stock' => array_sum($attrStockArr),
            'is_sold' => min($attrStockArr) ? 0 : 1
        ]);

        return [$skuList, $id, $is_new, $data];
    });

    event('product.create', [$id, $data, $skuList, $is_new, $slider_image, $description, $is_copy, $relationData]);

    $this->dao->cacheTag()->clear();
    $storeProductAttrServices->cacheTag()->clear();
}

这里会处理:

text 复制代码
商品主表
商品详情
SKU 规格
虚拟商品卡密
库存汇总
标签、分类、品牌、保障、参数、优惠券关联
商品创建事件
商品缓存和规格缓存

所以 AI 生成内容后,不能绕过 saveData() 直接改表。更推荐的方式是:AI 只生成候选草稿,运营确认后仍走原商品保存接口。

4. 商品标签为什么也不能乱改

商品编辑详情里,标签会被还原成可展示结构:

php 复制代码
if ($productInfo['label_id']) {
    $label_id = is_array($productInfo['label_id'])
        ? $productInfo['label_id']
        : explode(',', $productInfo['label_id']);

    $productInfo['label_id'] = $userLabelServices->getLabelList(
        ['ids' => $label_id],
        ['id', 'label_name']
    );
} else {
    $productInfo['label_id'] = [];
}

if ($productInfo['store_label_id']) {
    $storeProductLabelServices = app()->make(StoreProductLabelServices::class);
    $productInfo['store_label_id'] = $storeProductLabelServices->getColumn(
        [['id', 'in', $productInfo['store_label_id']]],
        'id,label_name'
    );
} else {
    $productInfo['store_label_id'] = [];
}

商品保存时,标签又会进入关联数据:

php 复制代码
protected function getProductRelationData(array $data): array
{
    return [
        'cate_id' => $data['cate_id'] ?? [],
        'brand_id' => $data['brand_id'] ?? [],
        'store_label_id' => $data['store_label_id'] ?? [],
        'label_id' => $data['label_id'] ?? [],
        'ensure_id' => $data['ensure_id'] ?? [],
        'specs_id' => $data['specs_id'] ?? [],
        'coupon_ids' => $data['coupon_ids'] ?? [],
    ];
}

因此 AI 标签建议要遵守一个原则:

text 复制代码
AI 可以建议"应该打什么标签"
系统必须校验"标签是否存在、是否属于当前平台或供应商、是否允许绑定"
运营最终确认后,再进入原商品保存链路

不要让 AI 直接生成不存在的标签 ID,更不要让 AI 直接写 store_label_id

5. 一个安全的 AI 商品运营流程

推荐流程如下:

text 复制代码
1. 后台选择商品
2. 读取商品名称、简介、分类、品牌、SKU、已有标签
3. 调用 chat_ai 生成候选标题、卖点、标签建议
4. 保存为"AI 草稿",不改商品正式数据
5. 运营勾选采用项
6. 前端带着完整商品表单提交原保存接口
7. StoreProductServices::saveData() 完成校验、事务、事件和缓存清理

如果要新增一个草稿服务,可以按项目分层写成:

php 复制代码
class AiProductDraftServices extends BaseServices
{
    /**
     * 生成商品运营草稿
     * @param int $productId 商品ID
     * @param array $input 运营输入
     * @return array
     */
    public function makeDraft(int $productId, array $input): array
    {
        $product = app()->make(StoreProductServices::class)->getInfo($productId);

        $prompt = [
            'store_name' => $product['store_name'] ?? '',
            'cate_name' => $product['cate_name'] ?? [],
            'brand_name' => $product['brand_name'] ?? '',
            'store_label' => $product['store_label_id'] ?? [],
            'operator_require' => $input['message'] ?? '',
        ];

        $result = app()->make(AiServices::class)->AiType([
            'type' => 'product_name',
            'message' => json_encode($prompt, JSON_UNESCAPED_UNICODE),
            'store_name' => $product['store_name'] ?? '',
        ]);

        return [
            'product_id' => $productId,
            'draft_title' => $result,
            'notice' => 'AI 结果仅作为候选,保存商品前必须人工确认',
        ];
    }
}

这段是二开思路,不建议把它塞到 Controller。后续如果要落库,也应该新增对应 Dao/Model 或复用项目已有草稿表能力。

6. Prompt 要限制输出结构

项目里的 productName() 已经做了一件很关键的事:要求 AI 只返回固定 JSON。

php 复制代码
public function productName($data)
{
    $systemContent = '
请严格按此规范执行:
1. 核心指令:
   • 生成3条差异化标题,每条30-50字,符合电商平台SEO规则
   • 必须包含:核心关键词(前10字)+核心卖点(材质/功能)+附加价值
   • 禁止:重复词、违禁词、模糊描述
3. 输出格式:仅返回JSON,键名固定为t1/t2/t3:
{
  "t1": "标题1",
  "t2": "标题2",
  "t3": "标题3"
}';

    $userContent = $data['message'];
    $result = $this->services->ai()->chat($systemContent, $userContent);

    return array_values(json_decode($result, true));
}

二开时建议继续保留这种思路:

text 复制代码
只返回 JSON
字段固定
不让 AI 输出 SQL
不让 AI 输出 HTML 脚本
不让 AI 生成商品 ID、标签 ID、价格、库存这类关键数值

7. 哪些字段适合 AI 生成,哪些字段必须人工确认

适合 AI 生成候选:

text 复制代码
商品标题
商品简介
分享文案
卖点短句
规格参数模板
标签建议
详情页段落草稿

必须人工确认:

text 复制代码
价格
库存
成本价
会员价
运费模板
商品分类
商品标签 ID
优惠券绑定
供应商归属
上下架状态
审核状态
活动关联

尤其是库存、价格、活动关联,不建议交给 AI 自动写入。

8. 注意事项

  1. AI 生成结果必须做 JSON 解析失败兜底,不能默认模型一定按格式返回。
  2. 不要把后台管理员信息、用户手机号、订单收货信息、敏感配置传给 AI。
  3. 商品正式保存必须走 StoreProductServices::saveData(),不要为了省事直接更新商品表。
  4. 标签建议要用标签名称匹配,再由系统查出合法标签 ID。
  5. AI 只做候选内容,不直接发布、不直接上架、不直接改库存价格。
  6. 生成内容要保留操作日志,至少记录商品 ID、管理员 ID、生成类型、采用状态。
  7. 供应商商品要继续走 checkSupplierProductAuth() 权限边界。

标签建议

text 复制代码
CRMEB Pro
二次开发
AI
商城系统
商品运营
ThinkPHP
PHP